如何使用PowerMock模拟返回void的静态方法?

92

我在项目中有一些静态工具方法,其中一些只是传递或抛出异常。有很多示例可以展示如何模拟返回类型不为void的静态方法。但是如何模拟返回void的静态方法来执行"doNothing()"呢?

非void版本使用以下代码:

@PrepareForTest(StaticResource.class)

...

PowerMockito.mockStatic(StaticResource.class);

...

Mockito.when(StaticResource.getResource("string")).thenReturn("string");

然而,如果应用于返回voidStaticResources,编译器会抱怨when(T)不适用于void...

有什么想法吗?

一个解决方法可能是让所有静态方法都返回一些Boolean以表示成功,但我不喜欢变通方法。


这个问题是关于 PowerMockito 还是 PowerMock 的?标题表明是 PowerMock,但看起来你正在使用 PowerMockito。 - xpioneer
5个回答

99
你可以这样 桩化 一个静态的 void 方法:
PowerMockito.doNothing().when(StaticResource.class, "getResource", anyString());

虽然我不确定你为什么要这样做,因为当你调用 mockStatic(StaticResource.class) 时,StaticResource 中的所有静态方法默认都被存根。

更有用的是,你可以像这样捕获传递给 StaticResource.getResource() 的值:

ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
PowerMockito.doNothing().when(
               StaticResource.class, "getResource", captor.capture());

你可以像这样评估传递给 StaticResource.getResource 方法的字符串:

String resourceName = captor.getValue();

3
能够做到这一点非常酷,因为它让你可以模拟Thread.sleep(long millis)以不同的时间/更短的时间/根本不睡眠! - fragorl
有没有办法避免在方法中使用字符串,比如你的示例中的“getResource”? - Casper Thule Hansen
@CasperThuleHansen 你可以潜在地使用反射并获取方法名称。 - user1697575
以上方法的最后一部分是参数,可以根据需要用逗号分隔多个参数。 - Smart Coder

49

自Mockito 3.4.0版本开始,引入了一种实验性API来模拟静态方法。

以下示例代码已经在Mockito 4.3.1(testImplementation("org.mockito:mockito-inline:4.3.1"))和JUnit Jupiter 5.8.2,OpenJDK 11上进行了测试。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.util.UUID;

public class StaticMockTest {

    @Test
    void showCaseStaticMock() {
        try (MockedStatic<StaticMockTest> staticMock = Mockito.mockStatic(StaticMockTest.class)) {
            staticMock.when(StaticMockTest::getUUIDValue).thenReturn("Mockito");
            Assertions.assertEquals("Mockito", StaticMockTest.getUUIDValue());
        }

        // Regular UUID
        UUID.fromString(StaticMockTest.getUUIDValue());
    }

    public static String getUUIDValue() {
        return UUID.randomUUID().toString();
    }
}

之前的回答,可能是使用Mockito 1.x/2.x和Powermock 1.x/2.x

你可以像在真实实例上一样使用Mockito进行设置。例如,你可以链接存根,以下一行代码将使第一个调用不做任何操作,然后对getResources的第二个及以后的调用将引发异常:

// the stub of the static method
doNothing().doThrow(Exception.class).when(StaticResource.class);
StaticResource.getResource("string");
    
// the use of the mocked static code
StaticResource.getResource("string"); // do nothing
StaticResource.getResource("string"); // throw Exception

感谢Matt Lachman的一句话提醒,注意如果在模拟创建时未更改默认答案,则模拟默认情况下不会执行任何操作。因此,编写以下代码等效于不编写它。

doNothing().doThrow(Exception.class).when(StaticResource.class);
    StaticResource.getResource("string");

尽管如此,对于将要读取测试的同事来说,您期望在这个特定的代码中什么也不期望会是有趣的。当然,这取决于测试的可理解性,可以进行相应的调整。


顺便说一下,在我看来,如果你正在编写新代码,应该避免模拟静态代码。在 Mockito 中,我们认为这通常是糟糕设计的提示,可能导致难以维护的代码。不过,对于现有的遗留代码来说,又是另一回事。

一般来说,如果需要模拟私有或静态方法,则表示该方法做了太多的事情,并且应该将其外部化为一个对象,该对象将被注入到测试对象中。

希望能对您有所帮助。

敬礼


2
不幸的是,这样做行不通,因为when()只接受变量,而StaticResource是一种类型。(“StaticResource无法解析为变量”) - Pete
@MattLachman 谢谢,我发现了 API 的不清晰使用。答案中的代码行将使模拟在第一次调用时什么也不做,然后在第二次调用时,模拟将抛出异常。你确实是对的:默认情况下,模拟方法默认情况下什么也不做(如果在模拟创建时未更改默认值)。话虽如此,为了测试协作者的文档目的,编写“doNothing”行为甚至可以是一个好主意,当然这取决于测试并留给程序员判断。 - bric3
这仍然不起作用。当我尝试 PowerMockito.verifyStatic(times(4)) 时,无论我是说 times(0)times(1) 等,它总是通过。 - stantonk
@stantonk verifyStatic 不是关于存根,而是关于验证调用的,如果您有示例,我可以帮助您。 - bric3
@Brice 谢谢,我已经弄明白了,文档没有清楚说明您必须在模拟的静态对象上调用预期的调用。 - stantonk
显示剩余7条评论

21

简单来说,想象一下你想要嘲笑以下这行:

StaticClass.method();

接下来,您需要编写以下代码来进行模拟:

PowerMockito.mockStatic(StaticClass.class);
PowerMockito.doNothing().when(StaticClass.class);
StaticClass.method();

3
它可以适用于 StaticClass.class 的多个方法,例如: StaticClass.method(); StaticClass.method1(); StaticClass.method2(); - S Kumar

7

为了模拟一个返回void的静态方法,例如Fileutils.forceMKdir(File file),

示例代码:

File file =PowerMockito.mock(File.class);
PowerMockito.doNothing().when(FileUtils.class,"forceMkdir",file);

1

您可以使用mockitoStatic来模拟静态void方法,使用then(invocationOnMock -> null),例如:

@Test
void testStaticVoidMethod() {
        try (MockedStatic<FileUtils> mockedFileUtils = mockStatic(FileUtils.class)) {
            mockedFileUtils.when(() -> FileUtils.writeByteArrayToFile(any(), any())).then(invocationOnMock -> null);
            assertDoesNotThrow(() -> FileUtils.writeByteArrayToFile(new File("test.txt"), new byte[0]));
        }
    }

1
这应该是被接受的答案,非常感谢。 - Gremi64

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接