我写了一个工厂来产生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。是否有一个好的方法来模拟/验证这个特定的用例?
观察:当您在静态实体中调用静态方法时,您需要更改@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());
你可以做一点重构:
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);
}
}
避免使用无法避免的静态方法的典型策略是创建包装对象并使用包装对象。
包装器对象成为真正静态类的外观,您不需要测试它们。
包装器对象可以是这样的
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,将它作为协作器注入(甚至不需要复杂的构造函数进行测试),然后继续模拟。