Junit5如何模拟静态方法

39

我想在JUnit 5中模拟静态方法。但不幸的是,JUnit 5不支持Mockito。除了回到JUnit 4之外,是否有其他方法可以实现相同的功能?


1
从Mockito 3.4开始,可以直接实现。请查看我的答案和示例。 - angelcervera
6个回答

52

从Mockito 3.4.0(2020-07-10)开始,即使在JUnit 5中也可以直接模拟静态方法,无需使用任何扩展。

在文档中,你可以找到一个例子:48.模拟静态方法(自3.4.0起)

重要提示:需要使用 inline mock maker 。 因此,需要使用的依赖项不是核心依赖项:

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>3.4.6</version>
            <scope>test</scope>
        </dependency>

例子: 测试的类:

package teststatics;

public class FooWithStatics {
    public static Long noParameters() {
        return System.currentTimeMillis();
    }
    public static String oneParameter(String param1) {
        return param1.toUpperCase();
    }
}

测试类:

package teststatics;

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

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

public class FooWithStaticsTest {

    @Test
    void testStatic() {
        // Before mock scope, usual behavior.
        assertNotEquals(0L, FooWithStatics.noParameters());
        assertNotEquals("yyy", FooWithStatics.oneParameter("xxx"));

        // Mock scope
        try (MockedStatic mocked = mockStatic(FooWithStatics.class)) {

            // Mocking
            mocked.when(FooWithStatics::noParameters).thenReturn(0L);
            mocked.when(() -> FooWithStatics.oneParameter("xxx")).thenReturn("yyy");

            // Mocked behavior
            assertEquals(0L, FooWithStatics.noParameters());
            assertEquals("yyy", FooWithStatics.oneParameter("xxx"));

            // Verifying mocks.
            mocked.verify(times(1), FooWithStatics::noParameters);
            mocked.verify(times(1), () -> FooWithStatics.oneParameter("xxx"));
        }

        // After mock scope returns to usual behavior.
        assertNotEquals(0L, FooWithStatics.noParameters());
        assertNotEquals("yyy", FooWithStatics.oneParameter("xxx"));
    }
}

1
请参阅为什么Mockito不能mock静态方法?的答案,其中链接到了一个简单的三步示例的宣布博客文章。 - Gerold Broser
@angelcervera 使用mockedStatic如何模拟一个静态void方法?对于返回类型为void的方法,我不能使用上述指令。 - Raghavendra S S
1
@angelcervera 的回答非常有帮助。我曾经苦苦挣扎,不得不使用 jmockit 来模拟静态方法,但这确实简化了我的许多 JUnit5 测试。谢谢 :) - Xavier DSouza
只是额外补充,请确保 try 块完全被覆盖,否则无法进行模拟。 - Pratik Gaurav
如果你的静态方法是void类型的,会怎么样呢? - undefined
1
顶了一下,因为这个很有用,不过我要评论一下,我正在使用mockito-inline:4.8.1版本,使用这个API,mocked.verify(times(1), () -> FooWithStatics.oneParameter("xxx"))不再有效,而是必须改为mocked.verify(() -> FooWithStatics.oneParameter("xxx"), times(1)) - undefined

15

简短的回答是不可以,因为Mockito团队已经完成了他们的工作,正在等待JUnit团队提供一个扩展,并在这里进行了大量讨论。

但是,您可以通过一些额外的步骤来实现:由于JUnit 5支持运行传统的JUnit 4版本,因此您可以在JUnit 4中使用Mockito来创建测试用例,从而实现对这些情况的测试:

这里有一个迁移设置的示例项目,使用GradleMvn。我使用了PowerMock 2.0 beta和Mockito 2。


10
目前Mockito不提供静态方法mock的原因是因为人们普遍认为静态方法不需要被mock。但是,Mockito在这个问题上有一个未解决的问题,可以在此处参与讨论。虽然这不能解答你的问题,但它告诉你为什么你根本不需要这个功能,或者允许你加入讨论并提出你的想法。

一个常见的场景是我们需要模拟对System.getenv的调用,我并不认为这是一个不好的模式。如果我错了,请纠正我。 - Yeikel
@Yeikel,你提到的情况应该由UT框架处理,实际上可以使用JUnit4中的规则或JUnit5中的扩展来解决它。 - AR1
它并不是开箱即用的,这是事实,但是有一些聪明的方法可以通过扩展来实现。 - AR1
请问您能否提供任何相关参考资料?由于缺少这个功能,我们正在考虑降级到 Junit4。 - Yeikel
你不需要降级。最坏的情况下,你仍然可以使用由扩展调用的@rule。我会为你准备一个例子。 - AR1

5
  1. Make sure to have mockito-inline dependency in your POM file

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-inline</artifactId>
        <version>3.6.28</version>
        <scope>test</scope>
    </dependency>
    
  2. In my case I had to test scenario where exception thrown static method encode() of URLEncoder Class, so for that

    try (MockedStatic theMock  = mockStatic(URLEncoder.class)) {
        theMock.when(() -> URLEncoder.encode("Test/11", StandardCharsets.UTF_8.toString()))
        .thenThrow(UnsupportedEncodingException.class);
        when(restClient.retrieveByName("Test%2F11")).thenReturn(null);
        Assertions.assertThrows(ResponseStatusException.class, ()->service.retrieveByName("Test/11"));
    }
    

2
您可以使用mockito-inline组件来模拟静态方法。
  1. 添加Maven依赖项。
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.2.0</version>
    <scope>test</scope>
</dependency>

可选:如果遇到问题,排除mockito-core,因为它已经包含在mockito-inline中
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
        </exclusion>
        <exclusion>
            <artifactId>mockito-junit-jupiter</artifactId>
            <groupId>org.mockito</groupId>
        </exclusion>
    </exclusions>
</dependency>

3. 举个例子来说明:
class FilesUtility{
    public static boolean something(String x , String y){
        return true;
    }
}

测试案例:
@Test
void testStaticMethod() {
    try (MockedStatic<? extends FilesUtility> mocked = 
       mockStatic(FilesUtility.class, CALLS_REAL_METHODS)) {

    mocked
        when(() -> FilesUtility.something(any(), any()))
        .thenReturn(false);
    }
}

重要提示: 默认行为是什么都不做,但也可以在创建模拟对象时通过显式指定CALLS_REAL_METHODS来提供一个答案。
mockStatic(FilesUtility.class, CALLS_REAL_METHODS)

所以,如果您的模拟静态方法在内部调用了该模拟类中的任何其他方法,您不需要再模拟它们的行为 - 您可以通过指定CALLS_REAL_METHODS参数来保持它们的默认行为。这相当于对该类进行监视

0

我们可以使用JMockit来模拟静态方法。

JMockit用于模拟测试边界之外的外部依赖项,类似于Mockito和其他模拟库。 JMockit最重要的特点是它让我们可以模拟任何东西,即使是其他库难以模拟的东西,如构造函数、静态和final方法。它甚至允许模拟成员变量和初始化块。

按照以下步骤启用JMockit:

  1. JMockit工件位于中央Maven存储库中,在pom.xml中添加JMockit依赖项
    <!-- https://mvnrepository.com/artifact/org.jmockit/jmockit -->
    <dependency>
        <groupId>org.jmockit</groupId>
        <artifactId>jmockit</artifactId>
        <version>1.49</version>
        <scope>test</scope>
    </dependency>
  • 在TestClass中模拟类方法:

     public class TestClass{
     @Test
     public void testMethod() {
         new MockUp<ClassName>(){
             @Mock
             //在此处进行方法模拟
         };
     }
    

    }

  • 请跟随tutorial了解更多关于如何使用JMockit的内容。


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