web-dev-qa-db-ja.com

Mockitoで静的メソッドをモックする

私は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を使用しています。この特定のユースケースをモック/検証するための良い方法はありますか?

275
Naftuli Kay

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(...);

    }

詳しくは:

291
MariuszS

使用を避けることができない静的メソッドを避けるための典型的な戦略は、ラップされたオブジェクトを作成し、代わりにラッパーオブジェクトを使用することです。

ラッパーオブジェクトは実際の静的クラスのファサードになりますので、それらをテストすることはしません。

ラッパーオブジェクトは次のようになります。

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にして、そのことを共同作業者としてインジェクトするだけでよく(テストのために面倒なコンストラクタも必要ありません)、モックを続けます。

50
99Sono

前述のように、静的メソッドをmockitoでモックすることはできません。

テストフレームワークを変更することが選択肢ではない場合は、次の操作を実行できます。

DriverManager用のインターフェースを作成し、このインターフェースをモックし、ある種の依存性注入を介してそれを注入し、そのモックを検証します。

18
ChrisM

私は同様の問題を抱えていました。 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());
    }
}
15
6324

観察結果:静的エンティティ内で静的メソッドを呼び出すときは、@ 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());
6
some random guy

静的メソッドを偽装するには、Powermockを使用してください。 https://github.com/powermock/powermock/wiki/MockStatic 。 Mockito は提供していません この機能はあります。

Mockitoに関する記事をNiceで読むことができます。 http://refcardz.dzone.com/refcardz/mockito

6
marek.kapowicki

ちょっとしたリファクタリングでそれをすることができます:

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);
    }
}
5
Fermin Silva

私はまたMockitoとAspectJの組み合わせも書きました: https://github.com/iirekm/varia/tree/develop/ajmock

あなたの例は次のようになります。

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

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は独断的ではありません。静的モッキングのような非推奨のユースケースをブロックしたくありません。それは私たちの仕事ではありません。

2
David Miguel

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;
            }
         };
0
Zlatan