私は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を使用しています。この特定のユースケースをモック/検証するための良い方法はありますか?
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(...);
}
詳しくは:
使用を避けることができない静的メソッドを避けるための典型的な戦略は、ラップされたオブジェクトを作成し、代わりにラッパーオブジェクトを使用することです。
ラッパーオブジェクトは実際の静的クラスのファサードになりますので、それらをテストすることはしません。
ラッパーオブジェクトは次のようになります。
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にして、そのことを共同作業者としてインジェクトするだけでよく(テストのために面倒なコンストラクタも必要ありません)、モックを続けます。
前述のように、静的メソッドをmockitoでモックすることはできません。
テストフレームワークを変更することが選択肢ではない場合は、次の操作を実行できます。
DriverManager用のインターフェースを作成し、このインターフェースをモックし、ある種の依存性注入を介してそれを注入し、そのモックを検証します。
私は同様の問題を抱えていました。 PowerMockのmockStatic に関するドキュメントによると、私が変更した@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();
}
}
}
私のテストクラス:
@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());
静的メソッドを偽装するには、Powermockを使用してください。 https://github.com/powermock/powermock/wiki/MockStatic 。 Mockito は提供していません この機能はあります。
Mockitoに関する記事をNiceで読むことができます。 http://refcardz.dzone.com/refcardz/mockito
ちょっとしたリファクタリングでそれをすることができます:
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とAspectJの組み合わせも書きました: https://github.com/iirekm/varia/tree/develop/ajmock
あなたの例は次のようになります。
when(() -> DriverManager.getConnection(...)).thenReturn(...);
Mockitoは静的メソッドをキャプチャすることはできませんが、 Mockito 2.14.0以降 静的メソッドの呼び出しインスタンスを作成することでそれをシミュレートできます。
例( からの抜粋 ):
public class StaticMockingExperimentTest extends TestBase {
Foo mock = Mockito.mock(Foo.class);
MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
Method staticMethod;
InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
@Override
public Object call() throws Throwable {
return null;
}
};
@Before
public void before() throws Throwable {
staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
}
@Test
public void verify_static_method() throws Throwable {
//register staticMethod call on mock
Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
"some arg");
handler.handle(invocation);
//verify staticMethod on mock
//Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
//1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
// Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
verify(mock);
//2. Create the invocation instance using the new public API
// Mockito cannot capture static methods but we can create an invocation instance of that static invocation
Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
"some arg");
//3. Make Mockito handle the static method invocation
// Mockito will find verification mode in thread local state and will try verify the invocation
handler.handle(verification);
//verify zero times, method with different argument
verify(mock, times(0));
Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
"different arg");
handler.handle(differentArg);
}
@Test
public void stubbing_static_method() throws Throwable {
//register staticMethod call on mock
Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
"foo");
handler.handle(invocation);
//register stubbing
when(null).thenReturn("hey");
//validate stubbed return value
assertEquals("hey", handler.handle(invocation));
assertEquals("hey", handler.handle(invocation));
//default null value is returned if invoked with different argument
Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
"different arg");
assertEquals(null, handler.handle(differentArg));
}
static class Foo {
private final String arg;
public Foo(String arg) {
this.arg = arg;
}
public static String staticMethod(String arg) {
return "";
}
@Override
public String toString() {
return "foo:" + arg;
}
}
}
その目的は、静的モックを直接サポートすることではなく、その公開APIを改良して、 Powermockito のような他のライブラリが内部APIに頼らなくても、あるいはMockitoコードを直接複製しなくてもよいようにすることです。 ( ソース )
免責事項:Mockitoチームは、地獄への道は静的な方法で舗装されていると考えています。しかし、Mockitoの仕事はあなたのコードを静的メソッドから保護することではありません。あなたのチームが静的モックをしたくない場合は、組織内でPowermockitoを使用しないでください。 Mockitoは、Javaテストをどのように書くべきかについての見識のあるビジョンを持ったツールキットとして進化する必要があります(例えば、静的なモックをしないでください!!!)。しかし、Mockitoは独断的ではありません。静的モッキングのような非推奨のユースケースをブロックしたくありません。それは私たちの仕事ではありません。
JMockitフレームワークを使う 。それは私のために働きました。 DBConenction.getConnection()メソッドをモックするためのステートメントを書く必要はありません。以下のコードで十分です。
以下の@Mockはmockit.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;
}
};