java mockito中文 - 用Mockito嘲笑靜態方法




mockito教學 junit (8)

我寫了一個工廠來生成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來測試我的測試用例。 有沒有一種好的方法來模擬/驗證這個特定的用例?


Answers

我有類似的問題。 我接受的答案不適用於我,直到我做出更改: @PrepareForTest(TheClassYouWriteTestFor.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();
        }
    }
}

我的測試課:

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


觀察:當您在靜態實體中調用靜態方法時,您需要更改@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());

如前所述,你不能用mockito來嘲笑靜態方法。

如果更改您的測試框架不是一個選項,您可以執行以下操作:

為DriverManager創建一個接口,模擬這個接口,通過某種依賴注入來注入它並驗證該模擬。


在Mockito上使用PowerMockito

示例代碼:

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

    @Test
    public void testName() throws Exception {

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

        //when
        sut.execute();

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

    }

更多信息:


避免使用靜態方法的典型策略是通過創建包裝對象並使用包裝器對象來代替。

包裝對象成為真正的靜態類的外觀,並且你不會測試它們。

包裝器對象可能是類似的東西

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,將其註入為合作者(你甚至不需要混亂的構造函數進行測試),然後繼續進行嘲諷。


你可以通過一些重構來完成它:

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

Mockito返回對象,但靜態意味著“類級別,而不是對象級別”所以mockito會給靜態的空指針異常。





java unit-testing mocking mockito