如何在没有使用PowerMock的情况下模拟静态方法

52

在JUnit测试中,我们有没有办法模拟静态工具方法?

我知道Powermock可以模拟静态调用,但我不想使用Powermock。

有没有其他替代方案?


1
即使使用Mockito,您也无法模拟静态方法。 - Abubakkar
你不想使用PowerMock的原因是什么? - Uwe Allner
1
你必须模拟它吗?这是你的代码,你可以重写它,以便没有静态方法吗?静态方法对于可测试性来说是致命的 - Arek
这些是其他模块使用的实用类,所以我不能更改。 - gati sahu
1
@UweAllner,不使用PowerMock的一个原因是代码覆盖工具,例如JaCoCo,在代码覆盖报告中可能不会考虑通过PowerMock覆盖的代码。 - Dexter
4个回答

39

(我假设你会使用Mockito) 我脑海中没有特别的解决方法,但在类似情况下,我倾向于使用以下策略:

1) 在被测试的类中,将静态直接调用替换为调用包级别的方法,并由该方法包装静态调用本身:

public class ToBeTested{

    public void myMethodToTest(){
         ...
         String s = makeStaticWrappedCall();
         ...
    }

    String makeStaticWrappedCall(){
        return Util.staticMethodCall();
    }
}

2) 在测试过程中监视被测试的类,模拟封装的包级方法:

public class ToBeTestedTest{

    @Spy
    ToBeTested tbTestedSpy = new ToBeTested();

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void myMethodToTestTest() throws Exception{
       // Arrange
       doReturn("Expected String").when(tbTestedSpy).makeStaticWrappedCall();

       // Act
       tbTestedSpy.myMethodToTest();
    }
}

这里是我写的一篇关于间谍活动的文章,其中包括类似案例,如果您需要更多见解,请访问:sourceartists.com/mockito-spying


当问题明确要求静态方法时,如何调用构造函数?当所有方法都是静态的时候,调用构造函数不是无用的吗?在这种情况下,间谍也将是无用的。 - Talha
1
如果这个类不是final的,他可以继承ToBeTested并添加包装方法和测试子类。 - thomas77
当我试图验证方法是否被调用时,出现了错误 Wanted but not invoked: Actually, there were zero interactions with this mock. - 6rchid

11

当您在单元测试中遇到麻烦的 静态 代码时,觉得需要“模拟”掉它,您有以下几种选择:

  • 您可以使用 PowerMock(ito)。效果很好。
  • 您可以使用 JMockit。也很好用。
  • 如果您正在测试自己编写的代码 ,您可能需要退后一步问自己:“为什么我会写出现在难以单元测试的代码?”

换句话说:如果要使用模拟框架,则必须使用上述其中之一。从一方面来说,这是绝对公平的。静态 是 Java 语言的一部分;那么为什么不使用一个允许您处理它的框架呢?

但是当然,您的生产代码中仍然存在静态调用。这导致紧密耦合并防止多态性。

因此,如果您能够消除静态调用(即使只是使用其他答案中建议的解决方法),那就更好了。如果不能,Mockito 无法帮助您。您需要字节码操作或 JVM 代理的魔法。


9
你可以使用 Mockito(自 3.4.0 版本起)来模拟静态方法。
假设有一个类 Foo:
class Foo{
  static String method() {
    return "foo";
  }
}

这是测试:

@Test
void testMethod() {
    assertEquals("foo", Foo.method());
    try (MockedStatic mocked = Mockito.mockStatic(Foo.class)) {
        mocked.when(Foo::method).thenReturn("bar");
        assertEquals("bar", Foo.method());
        mocked.verify(Foo::method);
    }
    assertEquals("foo", Foo.method());
}

这需要依赖项org.mockito:mockito-inline:3.4.0或更新版本。


我使用了 org.mockito:mockito-inline:3.4.0 和更新版本,并复制了与上面相同的代码。但是仍然出现“MockedStatic”和“mockStatic”的编译错误,它没有显示任何导入语句。我尝试使用 import org.mockito.*; 但它不起作用。 - user2000189
在使用编辑后的答案后,我遇到了编译错误:“MockedStatic无法解析为类型”。我们需要任何特殊的导入语句吗?我的pom.xml依赖项已添加如下: <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>3.5.9</version> </dependency> - user2000189
强制使用mockito-core 3.4.0版本可以让您运行此示例,但请注意其他潜在问题,因为它尚未得到Spring的官方支持。 - Marc
有没有办法在单个 try 块中模拟多个类?@Marc - VishnuVS
try with 块被用来初始化和清理对象。所以你可以像下面这样做: try (MockedStatic<Foo1> mocked1 = Mockito.mockStatic(Foo1.class); MockedStatic<Foo2> mocked2 = Mockito.mockStatic(Foo2.class) ) { ...... } - David Hladky
显示剩余2条评论

2

我在类似于Maciej在上面回答中提出的建议方面取得了很多成功。在Java8中,我喜欢使用函数接口来包装那些静态方法,使它们更容易注入或模拟。例如:

Original Answer翻译成"最初的回答"

public class MyClass {
    private MyStaticWrapper staticWrapper;

    public MyClass(final MyStaticWrapper staticWrapper) {
        this.staticWrapper = staticWrapper;
    }

    public void main() {
        ...
        staticWrapper.doSomething();
        ...    
    }
}    

public interface MyStaticWrapper {
    default void doSomething() {
      Util.annoyingUntestableStaticFunction();
    }
}

2
你好,你有这个单元测试的例子吗? - NinjaDev

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