用Mockito模拟静态方法

Naf*_*Kay 321 java unit-testing mocking mockito

我写了一个工厂来生产java.sql.Connection物品:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return DriverManager.getConnection(...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我想验证传递给的参数DriverManager.getConnection,但我不知道如何模拟静态方法.我正在使用JUnit 4和Mockito来测试我的测试用例.有没有一种很好的方法来模拟/验证这个特定的用例?

Mar*_*szS 324

在Mockito上使用PowerMockito.

示例代码:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

    @Test
    public void shouldVerifyParameters() throws Exception {

        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

        //when
        sut.execute(); // System Under Test (sut)

        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);

    }
Run Code Online (Sandbox Code Playgroud)

更多信息:

  • 不幸的是,这方面的巨大缺点是需要PowerMockRunner. (32认同)
  • sut.execute()?手段? (16认同)
  • 仅供参考,如果您已经在使用JUnit4,可以执行`@RunWith(PowerMockRunner.class)`以及`@PowerMockRunnerDelegate(JUnit4.class)`. (8认同)
  • System Under Test,需要模拟DriverManager的类.http://www.kaczanowscy.pl/tomek/2011-01/testing-basics-sut-and-docs (4认同)
  • 虽然这在理论上有效,但[实践中很难](/sf/ask/1479008331/)... (3认同)

99S*_*ono 60

避免使用无法避免使用的静态方法的典型策略是创建包装对象并使用包装器对象.

包装器对象成为真实静态类的外观,您不会测试它们.

包装器对象可能是这样的

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,您的测试类可以使用此单例对象,例如,具有用于实际使用的默认构造函数:

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** constructor used by CDI or whatever real life use case */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** constructor used in tests*/
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,您有一个可以轻松测试的类,因为您不直接使用静态方法的类.

如果您正在使用CDI并且可以使用@Inject注释,则更容易.只需让你的Wrapper bean @ApplicationScoped,将这个东西注入协作者(你甚至不需要凌乱的构造函数进行测试),然后继续进行模拟.

  • 我创建了一个工具来自动生成包含静态调用的Java 8"mixin"接口:https://github.com/aro-tech/interface-it生成的mixins可以像任何其他接口一样进行模拟,或者如果你的类正在测试中"实现"接口,您可以覆盖子类中的任何方法以进行测试. (3认同)

leo*_*kom 60

自 Mockito 3.4.0 起,可以在 Mockito 中模拟静态方法。有关更多详细信息,请参阅:

https://github.com/mockito/mockito/releases/tag/v3.4.0

https://github.com/mockito/mockito/issues/1013

在你的情况下,是这样的:

  @Test
  public void testStaticMockWithVerification() throws SQLException {
    try (MockedStatic<DriverManager> dummy = Mockito.mockStatic(DriverManager.class)) {
      DatabaseConnectionFactory factory = new MySQLDatabaseConnectionFactory();
      dummy.when(() -> DriverManager.getConnection("arg1", "arg2", "arg3"))
        .thenReturn(new Connection() {/*...*/});

      factory.getConnection();

      dummy.verify(() -> DriverManager.getConnection(eq("arg1"), eq("arg2"), eq("arg3")));
    }
  }
Run Code Online (Sandbox Code Playgroud)

注意:此功能需要 mockito-inline 依赖项。
对于 JUnit5:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>${mockito.version}</version>
  <scope>test</scope>
</dependency>
Run Code Online (Sandbox Code Playgroud)

  • 另请参阅 [Mockito 可以模拟静态方法!](https://asolntsev.github.io/en/2020/07/11/mockito-static-methods/) 和 https://github.com/mockito/mockito/pull /1955。 (2认同)
  • 请参阅此答案以获取带有参数的示例:/sf/answers/4530011/​​397/10318564 (2认同)

Chr*_*isM 21

如前所述,您无法使用mockito模拟静态方法.

如果无法更改测试框架,则可以执行以下操作:

为DriverManager创建一个接口,模拟这个接口,通过某种依赖注入注入它并验证该模拟.


632*_*324 20

我有类似的问题.@PrepareForTest(TheClassThatContainsStaticMethod.class)根据PowerMock的mockStatic文档,在我做出改变之前,接受的答案对我 不起作用.

我不必使用BDDMockito.

我的课:

public class SmokeRouteBuilder {
    public static String smokeMessageId() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("Exception occurred while fetching localhost address", e);
            return UUID.randomUUID().toString();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的考试班:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SmokeRouteBuilder.class)
public class SmokeRouteBuilderTest {
    @Test
    public void testSmokeMessageId_exception() throws UnknownHostException {
        UUID id = UUID.randomUUID();

        mockStatic(InetAddress.class);
        mockStatic(UUID.class);
        when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
        when(UUID.randomUUID()).thenReturn(id);

        assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • PowerMock.mockStatic &amp; Mockito.when 似乎不起作用。 (3认同)

Gay*_*tti 13

对于那些使用 JUnit 5 的人来说,Powermock 不是一个选择。您将需要以下依赖项才能仅使用 Mockito 成功模拟静态方法。

testCompile    group: 'org.mockito', name: 'mockito-core',           version: '3.6.0'
testCompile    group: 'org.mockito', name: 'mockito-junit-jupiter',  version: '3.6.0'
testCompile    group: 'org.mockito', name: 'mockito-inline',         version: '3.6.0'
Run Code Online (Sandbox Code Playgroud)

mockito-junit-jupiter 添加对 JUnit 5 的支持。

并且对模拟静态方法的支持由mockito-inline依赖提供。

例子:

@Test
void returnUtilTest() {
    assertEquals("foo", UtilClass.staticMethod("foo"));

    try (MockedStatic<UtilClass> classMock = mockStatic(UtilClass.class)) {

        classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");

        assertEquals("bar", UtilClass.staticMethod("foo"));
     }

     assertEquals("foo", UtilClass.staticMethod("foo"));
}
Run Code Online (Sandbox Code Playgroud)

try-with-resource 块用于使静态模拟保持临时状态,因此仅在该范围内模拟它。

不使用 try 块时,请确保在完成断言后关闭作用域模拟。

MockedStatic<UtilClass> classMock = mockStatic(UtilClass.class)
classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");
assertEquals("bar", UtilClass.staticMethod("foo"));
classMock.close();
Run Code Online (Sandbox Code Playgroud)

模拟无效方法:

mockStatic在一个类上调用时,该类中的所有静态 void 方法都会自动被模拟为doNothing().


小智 7

观察:当您在静态实体中调用静态方法时,需要在@PrepareForTest中更改类.

例如:

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);
Run Code Online (Sandbox Code Playgroud)

对于上面的代码,如果你需要模拟MessageDigest类,请使用

@PrepareForTest(MessageDigest.class)
Run Code Online (Sandbox Code Playgroud)

如果您有以下内容:

public class CustomObjectRule {

    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));

}
Run Code Online (Sandbox Code Playgroud)

然后,您需要准备此代码所在的类.

@PrepareForTest(CustomObjectRule.class)
Run Code Online (Sandbox Code Playgroud)

然后模拟方法:

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());
Run Code Online (Sandbox Code Playgroud)


Dup*_*ngh 7

我在 Mockito 中找到了一种解决方案。此功能仅来自以下版本:3.4.0

https://asolntsev.github.io/en/2020/07/11/mockito-static-methods/

  • 依赖性

    在你的build.gradle中用mockito-inline:3.4.0替换mockito-core:3.3.3:

    testImplementation('org.mockito:mockito-inline:3.4.0')
    
    Run Code Online (Sandbox Code Playgroud)
  • 我们要嘲笑什么

     class Buddy 
     {
         static String name() 
         {
            return "John";
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  • 模拟静态方法

        @Test
        void lookMomICanMockStaticMethods() 
        {
             assertThat(Buddy.name()).isEqualTo("John");
    
            try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) 
            {
                theMock.when(Buddy::name).thenReturn("Rafael");
                assertThat(Buddy.name()).isEqualTo("Rafael");
            }
    
            assertThat(Buddy.name()).isEqualTo("John");
        }
    
    Run Code Online (Sandbox Code Playgroud)

我认为这可以帮助我们。

  • 我不明白这里嘲笑有什么用。如果巴迪的话,它就抓不住了。name() 改变了。 (2认同)

mar*_*cki 6

要模拟静态方法,您应该使用Powermock查看:https: //github.com/powermock/powermock/wiki/MockStatic.Mockito 不提供此功能.

你可以读一篇关于mockito的文章:http: //refcardz.dzone.com/refcardz/mockito

  • 请不要链接到网站。答案应包括实际可用的答案。如果站点出现故障或更改,答案将不再有效。 (2认同)

Fer*_*lva 6

您可以通过一些重构来完成:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return _getConnection(...some params...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //method to forward parameters, enabling mocking, extension, etc
    Connection _getConnection(...some params...) throws SQLException {
        return DriverManager.getConnection(...some params...);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以扩展您的类MySQLDatabaseConnectionFactory以返回模拟连接,对参数执行断言等.

扩展类可以驻留在测试用例中,如果它位于同一个包中(我鼓励你这样做)

public class MockedConnectionFactory extends MySQLDatabaseConnectionFactory {

    Connection _getConnection(...some params...) throws SQLException {
        if (some param != something) throw new InvalidParameterException();

        //consider mocking some methods with when(yourMock.something()).thenReturn(value)
        return Mockito.mock(Connection.class);
    }
}
Run Code Online (Sandbox Code Playgroud)


iir*_*ekm 6

我还写了Mockito和AspectJ的组合:https : //github.com/iirekm/varia/tree/develop/ajmock

您的示例变为:

when(() -> DriverManager.getConnection(...)).thenReturn(...);
Run Code Online (Sandbox Code Playgroud)

  • 提供的链接已失效。 (4认同)

Dav*_*uel 6

Mockito 无法捕获静态方法,但从Mockito 2.14.0 开始,您可以通过创建静态方法的调用实例来模拟它。

示例(从他们的测试中提取):

public class StaticMockingExperimentTest extends TestBase {

    Foo mock = Mockito.mock(Foo.class);
    MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
    Method staticMethod;
    InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
        @Override
        public Object call() throws Throwable {
            return null;
        }
    };

    @Before
    public void before() throws Throwable {
        staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
    }

    @Test
    public void verify_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        handler.handle(invocation);

        //verify staticMethod on mock
        //Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
        //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
        //  Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
        verify(mock);
        //2. Create the invocation instance using the new public API
        //  Mockito cannot capture static methods but we can create an invocation instance of that static invocation
        Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        //3. Make Mockito handle the static method invocation
        //  Mockito will find verification mode in thread local state and will try verify the invocation
        handler.handle(verification);

        //verify zero times, method with different argument
        verify(mock, times(0));
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        handler.handle(differentArg);
    }

    @Test
    public void stubbing_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "foo");
        handler.handle(invocation);

        //register stubbing
        when(null).thenReturn("hey");

        //validate stubbed return value
        assertEquals("hey", handler.handle(invocation));
        assertEquals("hey", handler.handle(invocation));

        //default null value is returned if invoked with different argument
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        assertEquals(null, handler.handle(differentArg));
    }

    static class Foo {

        private final String arg;

        public Foo(String arg) {
            this.arg = arg;
        }

        public static String staticMethod(String arg) {
            return "";
        }

        @Override
        public String toString() {
            return "foo:" + arg;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

他们的目标不是直接支持静态模拟,而是改进其公共 API,以便其他库,如Powermockito,不必依赖内部 API 或直接复制一些 Mockito 代码。(来源

免责声明:Mockito 团队认为通往地狱的道路是由静态方法铺就的。但是,Mockito 的工作不是保护您的代码免受静态方法的影响。如果您不喜欢您的团队进行静态模拟,请停止在您的组织中使用 Powermockito。Mockito 需要发展为一个工具包,对如何编写 Java 测试有自己的看法(例如,不要模拟静态!!!)。然而,Mockito 并不是教条主义的。我们不想阻止不推荐的用例,如静态模拟。这不是我们的工作。