用Mockito模拟静态方法

 手机用户2502857113 发布于 2023-02-03 09:33

我写了一个工厂来生产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来测试我的测试用例.有没有一种很好的方法来模拟/验证这个特定的用例?

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

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

    包装器对象可能是这样的

    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,将这个东西注入协作者(你甚至不需要凌乱的构造函数进行测试),然后继续进行模拟.

    2023-02-03 09:34 回答
  • 我有类似的问题.@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();
            }
        }
    }
    

    我的考试班:

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

    2023-02-03 09:34 回答
  • 如前所述,您无法使用mockito模拟静态方法.

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

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

    2023-02-03 09:34 回答
  • 在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(...);
    
        }
    

    更多信息:

    为什么Mockito不会模拟静态方法?

    2023-02-03 09:36 回答
  • 您可以通过一些重构来完成:

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

    2023-02-03 09:36 回答
  • 要模拟静态方法,您应该使用Powermock查看:https: //github.com/powermock/powermock/wiki/MockStatic.Mockito 不提供此功能.

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

    2023-02-03 09:37 回答
  • 观察:当您在静态实体中调用静态方法时,需要在@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());
    

    2023-02-03 09:37 回答
  • 我还写了Mockito和AspectJ的组合:https : //github.com/iirekm/varia/tree/develop/ajmock

    您的示例变为:

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

    2023-02-03 09:37 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有