Mockitoによる静的メソッドのモック


373

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


1
これは役に立ちますか?stackoverflow.com/questions/19464975/...
sasankad

5
モッキートをデザインすることはできません:)
MariuszS

25
@MariuszS Mockito(またはEasyMock、jMock)がモッキングstaticメソッドをサポートしていないのは仕様ではなく、偶然です。この制限(モックfinalクラス/メソッド、またはnew-edオブジェクトのサポートなし)は、モックを実装するために採用されるアプローチの自然な(ただし意図しない)結果です。他のモッキングライブラリは、これらの制限を回避する他のアプローチを使用します。これは.NETの世界でも起こりました。
ホジェリオ

2
@Rogério説明ありがとうございます。github.com/mockito/mockito/wiki/FAQ 静的メソッドをモックできますか? いいえ。Mockitoは、理解や変更が難しい静的な手続き型コードよりもオブジェクト指向と依存性注入を優先します。この制限の背後にもいくつかのデザインがあります:)
MariuszS 2015年

17
@MariuszS私は、ツールを認めるのではなく正当なユースケースを却下しようとする試みには、(簡単に)削除できない制限があり、正当な理由を提供しないと、それを読みました。ちなみに、ここでは、反対の観点からの参考資料を交えた議論を紹介します。
ホジェリオ

回答:


350

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

    }

詳しくは:


4
これは理論的には機能しますが、実際には苦労します ...
Naftuli Kay

38
残念ながら、これの大きな欠点は、PowerMockRunnerが必要なことです。
Innokenty 2014年

18
sut.execute()?手段?
TejjD 2015

4
System Under Test、DriverManagerのモックが必要なクラス。kaczanowscy.pl/tomek/2011-01/testing-basics-sut-and-docs
MariuszS

8
参考までに、すでにJUnit4を使用している場合は@RunWith(PowerMockRunner.class)、それ以下で実行できます@PowerMockRunnerDelegate(JUnit4.class)
EM作成

71

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

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

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

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にして、そのことをコラボレーターとして注入し(テストのために面倒なコンストラクターも必要ない)、モックを続けるだけです。


3
静的呼び出しをラップするJava 8「ミックスイン」インターフェースを自動的に生成するツールを作成しました:github.com/aro-tech/interface-it生成されたミックスインは、他のインターフェースと同じようにモックすることができます。テスト用のサブクラス内の任意のメソッドをオーバーライドできるインターフェイス。
aro_tech 2016年

25

同様の問題がありました。mockStaticに関するPowerMockのドキュメントに@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());
    }
}

現在JUnit 4を使用している?.mockStaticと?.whenを把握できない
Teddy

PowerMock.mockStatic&Mockito.whenが機能していないようです。
テディ

これを後で見る人のために、私はPowerMockito.mockStatic(StaticClass.class);と入力する必要がありました。
思想家

powermock-api-mockito maven arterfactを含める必要があります。
PeterS

23

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

テストフレームワークを変更するオプションがない場合は、次の操作を実行できます。

DriverManagerのインターフェースを作成し、このインターフェースをモックし、ある種の依存関係注入を介して挿入し、そのモックを確認します。


7

観察:静的エンティティ内で静的メソッドを呼び出す場合は、@ 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());

私は静的クラスがモックしない理由を理解しようとして壁に頭をぶつけていました。インターウェブのすべてのチュートリアルでは、ONEは必要最低限​​のユースケースよりも多くのことを考えているはずです。
SoftwareSavant

6

あなたは少しのリファクタリングでそれを行うことができます:

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

6

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

あなたはmockitoについての素敵な記事を読むことができます:http://refcardz.dzone.com/refcardz/mockito


2
ウェブサイトにリンクしないでください。回答には、実際に使用可能な回答を含める必要があります。サイトがダウンしたり変更されたりすると、回答は無効になります。
the_new_mr 2018

6

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

彼らの目標は、静的モッキングを直接サポートすることではなく、Powermockitoなどの他のライブラリが内部APIに依存したり、Mockitoコードを直接複製したりする必要がないように、パブリックAPIを改善することです。(ソース

免責事項:Mockitoチームは、地獄への道は静的メソッドで舗装されていると考えています。ただし、Mockitoの仕事は、静的メソッドからコードを保護することではありません。チームが静的なモックを実行したくない場合は、組織でのPowermockitoの使用を停止してください。Mockitoは、Javaテストをどのように記述する必要があるかについての見識のあるツールキットとして進化する必要があります(たとえば、静的モデルをモックしないでください!!!)。ただし、モッキートは独断的ではありません。静的なモックのような非推奨のユースケースをブロックしたくありません。それは私たちの仕事ではありません。



1

そのメソッドは静的なので、使用するために必要なすべてのものがすでにあるので、モックの目的を無効にします。静的メソッドをモックすることは悪い習慣だと考えられています。

これを行おうとすると、テストの実行方法に問題があることを意味します。

もちろん、PowerMockitoまたはそれを実行できるその他のフレームワークを使用できますが、アプローチを再考してみてください。

たとえば、静的メソッドが代わりに使用するオブジェクトをモック/提供しようとします。


0

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;
            }
         };
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.