用Mockito模拟静态方法

时间:2014-01-14 03:08:53

标签: 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);
        }
    }
}

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

15 个答案:

答案 0 :(得分:294)

在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(...);

    }

更多信息:

答案 1 :(得分:51)

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

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

包装器对象可能类似于

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

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

最后,您所测试的类可以使用此单例对象,例如, 有一个用于现实生活的默认构造函数:

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;
    }
}

这里有一个可以轻松测试的类,因为你没有直接使用静态方法的类。

如果您正在使用CDI并且可以使用@Inject注释,那么它就更容易了。 只需使用Wrapper bean @ApplicationScoped,将该东西作为协作者注入(你甚至不需要用于测试的混乱构造函数),然后继续进行模拟。

答案 2 :(得分:31)

从Mockito 3.4.0开始,可以在Mockito中模拟静态方法。 有关更多详细信息,请参见:

https://github.com/mockito/mockito/tree/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")));
    }
  }

注意:此功能需要模仿行内联。

答案 3 :(得分:19)

如前所述,你无法使用mockito模拟静态方法。

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

为DriverManager创建一个接口,模拟此接口,通过某种依赖注入和验证在该模拟上注入它。

答案 4 :(得分:15)

我有类似的问题。根据{{​​3}},在我做出更改之前,接受的答案对我不起作用:string

我不必使用@PrepareForTest(TheClassThatContainsStaticMethod.class)

我的课程:

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();
        }
    }
}

答案 5 :(得分:6)

要模拟静态方法,您应该使用Powermock查看: https://github.com/powermock/powermock/wiki/MockStatic。 Mockito doesn't provide此功能。

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

答案 6 :(得分:6)

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

例如:

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

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

@PrepareForTest(MessageDigest.class)

如果您有以下内容:

public class CustomObjectRule {

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

}

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

@PrepareForTest(CustomObjectRule.class)

然后嘲笑方法:

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());

答案 7 :(得分:5)

你可以通过一些重构来实现:

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...);
    }
}

然后你可以扩展你的类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);
    }
}

答案 8 :(得分:3)

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

你的例子变成了:

when(() -> DriverManager.getConnection(...)).thenReturn(...);

答案 9 :(得分:3)

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

的版本

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

  • 依赖

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

    testImplementation('org.mockito:mockito-inline:3.4.0')
    
  • 我们要模拟什么

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

        @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");
        }
    

我认为这可以帮助我们。

答案 10 :(得分:2)

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

示例(摘自their tests):

builder

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

  

免责声明:Mockito团队认为通往地狱的道路铺有静态方法。但是,Mockito的工作不是保护代码免受静态方法的侵害。如果您不希望团队进行静态模拟,请停止在组织中使用Powermockito。 Mockito需要发展为一个工具包,对如何编写Java测试抱有坚定的看法(例如,不要模拟静态!!!)。但是,Mockito不是教条。我们不想阻止不建议使用的情况,例如静态模拟。这不是我们的工作。

答案 11 :(得分:1)

由于该方法是静态的,因此它已经拥有使用它所需的所有内容,因此无法实现模拟的目的。 模拟静态方法被认为是不好的做法。

如果您尝试这样做,则意味着您要执行测试的方式存在问题。

当然,您可以使用PowerMockito或任何其他能够执行此操作的框架,但是请尝试重新考虑您的方法。

例如:尝试模拟/提供静态方法消耗的对象。

答案 12 :(得分:0)

使用JMockit框架。它为我工作。您无需编写用于模拟DBConenction.getConnection()方法的语句。只需下面的代码就足够了。

@下面的模拟是ockit.Mock包

Connection jdbcConnection = Mockito.mock(Connection.class);

MockUp<DBConnection> mockUp = new MockUp<DBConnection>() {

            DBConnection singleton = new DBConnection();

            @Mock
            public DBConnection getInstance() { 
                return singleton;
            }

            @Mock
            public Connection getConnection() {
                return jdbcConnection;
            }
         };

答案 13 :(得分:0)

有一个简单的解决方案,使用Java FunctionalInterface,然后将该接口添加为您要进行单元测试的类的依赖项。

答案 14 :(得分:0)

为了模拟静态函数,我可以这样做:

  • 在一些帮助类/对象中创建一个包装函数。 (使用名称变体可能有助于保持事物的分离和可维护性。)
  • 在您的代码中使用此包装器。 (是的,实现代码时需要考虑到测试。)
  • 模拟包装函数。

包装器代码片段(不是真正的功能,只是为了说明)

class myWrapperClass ...
    def myWrapperFunction (...) {
        return theOriginalFunction (...)
    }

当然,在单个包装类中积累多个这样的函数可能有利于代码重用。