我写了一个工厂来产生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。是否有一个好的方法来模拟/验证这个特定的用例?


当前回答

稍微重构一下:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {
    ConnectionSupplier connectionSupplier = () -> SupplierDriverManager.getConnection();

    public void setConnSupplier(ConnectionSupplier supplier) {
        this.connectionSupplier = supplier;
    }

    @Override 
    public Connection getConnection() {
        try {
            return connectionSupplier.conn();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @FunctionalInterface
    interface ConnectionSupplier {
        Connection conn();
    }
}

然后你可以使用mockito:

MySQLDatabaseConnectionFactory.ConnectionSupplier connectionSupplier = mock(MySQLDatabaseConnectionFactory.ConnectionSupplier.class);
when(connectionSupplier.conn()).thenReturn(yourMockObject);
yourConnectionFactory.setConnSupplier(connectionSupplier);

其他回答

我在《Mockito》中找到了一个解决方法。该特性仅在3.4.0版本中提供

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

dependency In your build.gradle replace mockito-core:3.3.3 by mockito-inline:3.4.0: testImplementation('org.mockito:mockito-inline:3.4.0') what are we going to mock class Buddy { static String name() { return "John"; } } Mock the static method @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"); }

我想这对我们有帮助。

在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不模拟静态方法?

稍微重构一下:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {
    ConnectionSupplier connectionSupplier = () -> SupplierDriverManager.getConnection();

    public void setConnSupplier(ConnectionSupplier supplier) {
        this.connectionSupplier = supplier;
    }

    @Override 
    public Connection getConnection() {
        try {
            return connectionSupplier.conn();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @FunctionalInterface
    interface ConnectionSupplier {
        Connection conn();
    }
}

然后你可以使用mockito:

MySQLDatabaseConnectionFactory.ConnectionSupplier connectionSupplier = mock(MySQLDatabaseConnectionFactory.ConnectionSupplier.class);
when(connectionSupplier.conn()).thenReturn(yourMockObject);
yourConnectionFactory.setConnSupplier(connectionSupplier);

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

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

包装器对象成为真正静态类的外观,您不需要测试它们。

包装器对象可以是这样的

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,将它作为协作器注入(甚至不需要复杂的构造函数进行测试),然后继续模拟。