Mockitoマッチャーはどのように機能しますか?


122

Mockito引数マッチャ(などはanyargThateqsame、及びArgumentCaptor.capture())Hamcrestのマッチャから非常に動作が異なります。

  • Mockitoマッチャーは、マッチャーが使用された後に長時間実行されるコードであっても、InvalidUseOfMatchersExceptionを頻繁に引き起こします。

  • Mockitoマッチャーは、特定のメソッドの1つの引数がマッチャーを使用する場合、すべての引数に対してMockitoマッチャーの使用のみを要求するなど、奇妙なルールに守られます。

  • Mockitoマッチャーは、Answersをオーバーライドしたり、使用したりすると、NullPointerExceptionを引き起こす可能性があります(Integer) any()

  • 特定の方法でMockitoマッチャーを使用してコードをリファクタリングすると、例外や予期しない動作が発生し、完全に失敗する可能性があります。

Mockitoマッチャーがこのように設計されている理由と、それらの実装方法を教えてください。

回答:


236

Mockitoマッチャーは静的メソッドであり、それらのメソッドへの呼び出しです。これらは、およびへの呼び出し中に引数の代わりwhenなりverifyます。

Hamcrestマッチャー(アーカイブバージョン)(またはHamcrestスタイルのマッチャー)は、オブジェクトがマッチャーの基準に一致した場合にtrueを返すMatcher<T>メソッドmatches(T)を実装および公開する、ステートレスな汎用オブジェクトインスタンスです。それらは副作用がないことを意図しており、一般的に以下のようなアサーションで使用されます。

/* Mockito */  verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));

Mockitoの照合プログラムは、Hamcrestスタイルの照合プログラムとは別に、存在マッチング表現の記述は、メソッド呼び出しに直接収まるようにMockitoマッチャーリターンTHamcrest照合方法はマッチャは、(タイプのオブジェクトを返しますMatcher<T>)。

Mockitoの照合プログラムは、次のような静的メソッドを介して呼び出されるeqanygt、及びstartsWithorg.mockito.Matchersorg.mockito.AdditionalMatchers。Mockitoバージョン間で変更されたアダプターもあります。

  • Mockito 1.xの場合、Matchers機能の一部の呼び出し(intThatまたはなどargThat)は、パラメータとしてHamcrestマッチャーを直接受け入れるMockitoマッチャーです。ArgumentMatcher<T>拡張org.hamcrest.Matcher<T>内部Hamcrest表現で使用されたされた、Hamcrest整合基本クラスの代わりにMockitoマッチャの任意の並べ替え。
  • Mockito 2.0以降では、MockitoはHamcrestに直接依存しなくなりました。Matchersフレーズとして呼び出されるintThatか、実装されなくなったが同様の方法で使用されるオブジェクトをargThatラップします。およびなどのHamcrestアダプターは引き続き使用できますが、代わりに移動しました。ArgumentMatcher<T>org.hamcrest.Matcher<T>argThatintThatMockitoHamcrest

マッチャーがHamcrestであるか単にHamcrestスタイルであるかに関係なく、それらは次のように適応できます。

/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));

上記のステートメントでfoo.setPowerLevelは、はを受け入れるメソッドですintis(greaterThan(9000))を返しますがMatcher<Integer>、これはsetPowerLevel引数として機能しません。MockitoマッチャーはintThat、そのHamcrestスタイルのマッチャーをラップし、引数として表示できるintようにを返します。Mockitoマッチャーは、サンプルコードの最初の行のように、式全体を単一の呼び出しにラップします。gt(9000)

マッチャーが行う/返すこと

when(foo.quux(3, 5)).thenReturn(true);

引数マッチャーを使用しない場合、Mockitoは引数の値を記録し、それらのequalsメソッドと比較します。

when(foo.quux(eq(3), eq(5))).thenReturn(true);    // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different

anyまたはgt(以上)のマッチャーを呼び出すと、Mockitoはマッチャーオブジェクトを格納します。これにより、Mockitoはその等価性チェックをスキップし、選択した一致を適用します。それがargumentCaptor.capture()後の検査のために代わりにその引数を保存するマッチャーを格納している場合。

マッチャーは、ゼロ、空のコレクション、などのダミー値を返しますnull。Mockitoは、0 anyInt()またはfor またはany(Integer.class)empty List<String>for などの安全で適切なダミー値を返そうとしanyListOf(String.class)ます。ただし、型の消去のため、Mockitoにはor 以外の値を返す型情報がありません。そのnullため、プリミティブ値を「自動アンボックス」しようとすると、NullPointerExceptionが発生する可能性があります。any()argThat(...)null

マッチャーはパラメーター値を好みeqgt取ります。理想的には、これらの値は、スタブ/検証が始まる前に計算する必要があります。別の呼び出しをモックしている最中にモックを呼び出すと、スタブが妨げられる可能性があります。

マッチャーメソッドは戻り値として使用できません。たとえば、Mockito thenReturn(anyInt())などthenReturn(any(Foo.class))でフレーズを使用する方法はありません。Mockitoは、スタブ呼び出しでどのインスタンスを返すかを正確に知る必要があり、任意の戻り値を選択することはありません。

実装の詳細

マッチャーは(Hamcrestスタイルのオブジェクトマッチャーとして)ArgumentMatcherStorageというクラスに含まれるスタックに格納されます。MockitoCoreとMatchersはそれぞれThreadSafeMockingProgressインスタンスを所有しています。このインスタンスは、MockingProgressインスタンスを保持するThreadLocal が静的に含まれています。具体的なArgumentMatcherStorageImplを保持するのは、このMockingProgressImplです。その結果、モックとマッチャーの状態は静的ですが、Mockitoクラスとマッチャークラスの間で一貫してスレッドスコープになります。

ほとんどのマッチャーコールは、のみなどのマッチャための例外を除いて、このスタックに追加しandorそして、not。これは、メソッドを呼び出す前に引数を左から右に評価するJava評価順序に完全に対応しています(依存しています)。

when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6]      [5]  [1]       [4] [2]     [3]

この意志:

  1. anyInt()スタックに追加します。
  2. gt(10)スタックに追加します。
  3. lt(20)スタックに追加します。
  4. 削除gt(10)lt(20)て追加しand(gt(10), lt(20))ます。
  5. foo.quux(0, 0)(他の方法でスタブされない限り)デフォルト値を返すCall false。内部的にはMockitoがquux(int, int)最新の通話としてマークします。
  6. Call when(false)は、引数を破棄しquux(int, int)、5で識別されたスタブメソッドの準備をします。有効な状態はスタック長が0(等号)または2(マッチャー)の2つだけで、スタックにはマッチャーが2つあります(ステップ1と4)。 Mockito any()は、最初の引数とand(gt(10), lt(20))2番目の引数にマッチャーを使用してメソッドをスタブ化し、スタックをクリアします。

これはいくつかのルールを示しています:

  • Mockitoは違い言うことができないquux(anyInt(), 0)としますquux(0, anyInt())。どちらもquux(0, 0)、スタックに1つのintマッチャーがある呼び出しのように見えます。したがって、1つのマッチャーを使用する場合は、すべての引数を照合する必要があります。

  • 呼び出し順序は重要なだけでなく、それがすべてを機能させるものです。通常、呼び出し順序が変更されるため、変数へのマッチャーの抽出は機能しません。ただし、メソッドへのマッチャーの抽出はうまく機能します。

    int between10And20 = and(gt(10), lt(20));
    /* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true);
    // Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt().
    
    public static int anyIntBetween10And20() { return and(gt(10), lt(20)); }
    /* OK */  when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true);
    // The helper method calls the matcher methods in the right order.
  • スタックは頻繁に変更されるため、Mockitoは非常に慎重にポリシングできません。Mockitoまたはモックとやり取りするときにのみスタックをチェックでき、すぐに使用されるか、誤って破棄されるかを知らずにマッチャーを受け入れる必要があります。理論的には、whenまたはへの呼び出し以外ではスタックは常に空である必要がありますverifyが、Mockitoはそれを自動的にチェックできません。で手動で確認できMockito.validateMockitoUsage()ます。

  • への呼び出しでwhen、Mockitoは実際に問題のメソッドを呼び出します。これは、メソッドをスタブして例外をスローした場合(またはゼロ以外または非null値が必要な場合)に例外をスローします。 doReturnおよびdoAnswer(など)実際のメソッドを呼び出さ、多くの場合、有用な代替手段です。

  • eqスタブの途中でモックメソッドを呼び出した場合(たとえば、マッチャーの回答を計算するため)、Mockitoは代わりにその呼び出しに対してスタック長をチェックし、失敗する可能性があります。

  • finalメソッドのスタブ/検証など、何か悪いことをしようとすると、Mockitoは実際のメソッドを呼び出し、追加のマッチャーをスタックに残しますfinalメソッドの呼び出しが例外をスローしないかもしれませんが、あなたが得ることがInvalidUseOfMatchersExceptionをモックと浮遊マッチャあなたの次の相互作用から。

一般的な問題

  • InvalidUseOfMatchersException

    • マッチャーをまったく使用する場合は、すべての引数にマッチャーコールが1つだけ含まれていること、およびwhenor verify呼び出しの外部でマッチャーを使用していないことを確認してください。マッチャーは、スタブ化された戻り値またはフィールド/変数として使用しないでください。

    • マッチャー引数を提供する一環としてモックを呼び出していないことを確認してください。

    • マッチャーを使用して最終メソッドをスタブ/検証していないことを確認します。これは、マッチャーをスタックに残すのに最適な方法であり、最終メソッドが例外をスローしない限り、これが、モックしているメソッドが最終であることを認識する唯一の場合です。

  • プリミティブ引数を持つNullPointerException: (Integer) any() nullをany(Integer.class)返し、0 を返します。これは、整数でNullPointerExceptionintなくを期待している場合に発生する可能性があります。いずれの場合もanyInt()、ゼロを返し、自動ボックス化ステップもスキップするを優先します。

  • NullPointerExceptionが発生または他の例外:の呼び出しwhen(foo.bar(any())).thenReturn(baz)、実際になります呼び出し foo.bar(null)ますがnull引数を受信した場合に例外をスローするスタブしている可能性があります。への切り替えdoReturn(baz).when(foo).bar(any()) は、スタブ化された動作スキップします

一般的なトラブルシューティング

  • MockitoJUnitRunnerを使用validateMockitoUsageするtearDownか、or @Afterメソッドを明示的に呼び出します(ランナーが自動的に行います)。これは、マッチャーを誤用したかどうかを判断するのに役立ちます。

  • デバッグの目的validateMockitoUsageで、コードにへの呼び出しを直接追加します。これは、スタックに何かがある場合にスローされます。これは、悪い症状の良い警告です。


2
この記事をありがとう。when / thenReturn形式のNullPointerExceptionが原因で、doReturn / whenに変更するまで問題が発生していました。
yngwietiger 2015年

11

私自身の問題の1つに対する解決策を探しているときにこの質問を見つけたので、Jeff Bowmanの優れた答えに少し追加しました。

メソッドの呼び出しが複数のモックのwhenトレーニング済みの呼び出しと一致する場合、呼び出しの順序whenは重要であり、最も広いものから最も具体的なものの順にする必要があります。ジェフの例の1つから始めます。

when(foo.quux(anyInt(), anyInt())).thenReturn(true);
when(foo.quux(anyInt(), eq(5))).thenReturn(false);

(おそらく)望ましい結果を保証する順序です:

foo.quux(3 /*any int*/, 8 /*any other int than 5*/) //returns true
foo.quux(2 /*any int*/, 5) //returns false

when呼び出しを逆にすると、結果は常にになりますtrue


2
これは有用な情報ですが、マッチャーはなくスタブに関するものであるため、この質問には意味がありません。順序は重要ですが、最後に定義された一致するチェーンが勝つという点でのみ重要です。これは、共存するスタブが多くの場合、最も固有性の高いものから最も低いものへと宣言されることを意味します。 、その時点で広い定義が最後に来る必要があるかもしれません。
Jeff Bowman

1
@JeffBowman質問はmockitoマッチャーに関するものであり、(ほとんどの例のように)スタブ時にマッチャーを使用できるため、この質問には意味があると思いました。グーグルで説明を検索してこの質問にたどり着いたので、ここにこの情報があると便利だと思います。
tibtof 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.