モッキートと調理方法

記事について



ここにMockitoの別のガイドがあります。 その一方で、私はこのライブラリの機能を説明しようと試みました。そのため、このライブラリに不慣れな読者は、単に一般的な考えではなく、すぐに完全に使用する機会を得ました。 一方、私はそれを十分にコンパクトで構造化し、すぐに全体を読み、一度読んだものの忘れられたものをすぐに見つけられるようにしたかった。 一般に、この記事は、私がこのライブラリに出会ったばかりで、それがどのように機能するのかを本当に理解していなかったときに、私にとっては役立つでしょう。







今では役に立つかもしれません-時々これのいくつかを忘れてしまいます。そして、公式の文書や他の人の記事ではなく、私自身によると、例えば概要を思い出すのが最も便利です。 同時に、私は主に最初からMockitoを探索するのに便利になるようにテキストを作成しようとしました。また、一部の場所では一見明らかなことを詳細に分析しました。







内容:



  1. Mockito:それは何であり、なぜそれが必要なのか
  2. 環境、バージョン、実験動物
  3. モックとスパイ
  4. 行動管理

    1. 通話条件を設定する
    2. 通話結果の設定
  5. メソッド呼び出しの追跡
  6. フィールド値およびMockitoアノテーションとしてのオブジェクトのモック
  7. デフォルトおよびMockitoセッションへのロールバック動作
  8. 他に何?


Mockito:それは何であり、なぜそれが必要なのか



つまり、Mockitoはスタブフレームワークです。







ご存じのように、コードをテストするとき(主にユニットテストだけでなく、主にユニットテスト)、テスト中の要素は、作業時に使用する必要があるクラスのインスタンスを提供する必要があります。 ただし、多くの場合、完全に機能する必要はありません。逆に、厳密に定義された方法で動作する必要があるため、動作は単純で完全に予測可能です。 それらはスタブと呼ばれます。 それらを取得するために、インターフェースの代替テスト実装を作成し、機能を再定義して必要なクラスを継承することができますが、これらはすべて非常に不便で、冗長で、エラーがたくさんあります。 あらゆる意味でより便利なソリューションは、スタブを作成するための特別なフレームワークです。 それらの1つ(そしておそらくJavaで最も有名なもの)はMockitoです。







Mockitoを使用すると、1行のコードで、クラスのいわゆるモック(目的のスタブの基礎のようなもの)を作成できます。 このようなモックの場合、作成直後の特定のデフォルトの動作は特徴的です(すべてのメソッドは以前に既知の値を返します-通常これはnull



または0



)。 この動作は、必要に応じて再定義したり、適切な詳細度で制御したりできます。 その結果、モックは必要なプロパティを持つスタブになります。 以下に、これを行う方法について詳しく説明します。







これらのクラスに対してもモックを作成できることに注意してください。特に、新しいインスタンスは、特にシングルトンクラスやユーティリティクラスなどのプライベートコンストラクターのみを備え、フレームワークと列挙型の最小限の構成でクラスを作成することはできません。







環境、バージョン、実験動物



この記事を書くとき、私は使用しました:









私の非人道的な実験のために、特定のデータへのアクセスを提供するサービスインターフェイスをここに書きました。







 public interface DataService { void saveData(List<String> dataToSave); String getDataById(String id); String getDataById(String id, Supplier<String> calculateIfAbsent); List<String> getData(); List<String> getDataListByIds(List<String> idList); List<String> getDataByRequest(DataSearchRequest request); }
      
      





そして、最後のインターフェイスメソッドに渡されるリクエストクラスのこのコード(順序のためです)。







 @AllArgsConstructor @Getter class DataSearchRequest { String id; Date updatedBefore; int length; }
      
      





データ単位はIDで識別され、いくつかの特性がありますが、サービスによって返される形式で直接、文字列であり、より複雑なオブジェクトではありません。 重要なことを見逃すことはありません。例はよりシンプルで明確です。







すぐに注意します。次の例では、明確にするためにモックオブジェクトのオーバーライドされたメソッドを直接呼び出しますが、実際のテストではアイデアはまったくありません! このテストでは、一貫して次のことを行います。









モックとスパイ



ほとんどの機能にアクセスすることになっているMockitoの中心​​クラスは、実際にはMockito



と呼ばれるクラスMockito



BDDにより適した形式でほぼ同じ機能を提供するBDDMockito



クラスもありますが、ここでは詳しく説明しません) 。 機能へのアクセスは、静的メソッドを介して実装されます。







DataService



クラスのモックを作成するには、次を実行するだけです。







 DataService dataServiceMock = Mockito.mock(DataService.class);
      
      





完了-必要なクラスのインスタンスを取得しました。 この型のパラメーターを必要とするメソッドまたはコンストラクター(たとえば、テストするクラスのコンストラクター)で受け入れられます。 中毒チェックが後でそれを待っていても、それを渡します: instanceof DataService



true



を返すだけでなく、 dataServiceMock.getClass()



-つまりDataService.class



も返しtrue



。 何らかの正式な方法で、モックオブジェクトを通常のモックオブジェクトとプログラムで区別することは、かなり難しいタスクであることが判明します。これは論理的です。結局、最初のオブジェクトは2番目のオブジェクトと区別できません。 ただし、Mockitoにはこのためのツール、 Mockito.mockingDetails



メソッドがあります。 任意のオブジェクトを渡すことで、 MockingDetails



クラスのオブジェクトを取得します。 Mockitoの観点からこのオブジェクトが何であるかについての情報が含まれています:モック、スパイ(下記参照)、使用方法、作成方法など。







特に注意すべきは、enumの最終クラスまたは模擬インスタンスのモックを作成しようとしたとき、またはfinalメソッドの動作をオーバーライドしようとしたときの状況です。 この場合、Mockitoのデフォルトの動作では、上記のコードは正確にこの状況を引用して動作を拒否します。 ただし、これは変更できます-プロジェクトに(プロジェクトディレクトリツリーの標準デバイスを使用して)ファイルtest/resources/mockito-extensions/org.mockito.plugins.MockMaker



、その中に次の行を入力します。







 mock-maker-inline
      
      





その後、通常の方法で最終クラスと列挙型を模倣し、最終メソッドをオーバーライドできます。







私が実際に得たモックは、可能な限り機能がありません。単一のメソッドが何かに影響することはなく、戻り値はオブジェクト型ではnull



、プリミティブ型では0



になります。 注:メソッドがコレクションを返す場合、デフォルトのモックはnull



ではなく、空のコレクションインスタンスを返します。 たとえば、 List



、実際のメソッドが何を返すかLinkedList



関係なくList



これは空のLinkedList



になります。 しかし、配列、プリミティブ、またはオブジェクトの値として、私はnull



を取得しnull



MockSettings



クラスの機能を使用して、デフォルトの動作(だけでなく)を変更できますが、これはほとんど必要ありません。







何らかの方法で、ほとんどの場合、デフォルトの動作は必要ありません。次のセクションでは、代わりに必要なものを設定する方法を詳細に分析します。







ただし、利用可能な機能を備えた実際のクラスのオブジェクトをスタブとして使用し、そのメソッドの一部のみの操作を再定義する場合はどうなりますか? 単体テストについて話している場合、そのような必要性は通常(常にではありませんが)、プロジェクトが設計に問題があることを示し、原則としてこれは推奨されません。 ただし、何らかの理由でこれを回避できない場合があります。 この場合、Mockitoにはいわゆるスパイ「スパイ」がいます。 モックとは異なり、クラスと完成したオブジェクトの両方に基づいて作成できます。







 DataService dataServiceSpy = Mockito.spy(DataService.class); // or DataService dataService = new DataService(); dataServiceSpy = Mockito.spy(dataService);
      
      





クラスに基づいてスパイを作成する場合、そのタイプがインターフェイスの場合、通常のモックオブジェクトが作成され、タイプがクラスの場合、Mockitoはデフォルトコンストラクター(パラメーターなし)を使用してインスタンスを作成しようとします。 そして、そのようなコンストラクターがない場合にのみ、エラーが発生し、テストは機能しません。







スパイオブジェクトのデフォルトの動作は、通常のクラスインスタンスの動作と同じですが、モックオブジェクトと同じ可能性があります。動作を再定義して使用を監視できます(次のセクションを参照)。 重要なポイント:スパイは、それが作成されたインスタンスのラッパーではありません! したがって、spyメソッドを呼び出しても、元のインスタンスの状態には影響しません。







行動管理



だから、私が必要なことをするためにモックやスパイを取得する方法について。 さらに、私は常にどこでも単に「モック」と書きます-特に明記しない限り、これは「モックまたはスパイ」を意味します。







一般に、モックオブジェクトの動作を制御することは、1つの明白な概念に帰着します:モックがそのような方法で影響を受けた場合(つまり、そのようなメソッドがそのような引数で呼び出された場合)、そのような方法で応答する必要があります。 このコンセプトには、Mockitoクラス内に2つの実装があります。可能な限り使用するために開発者が推奨するメイン実装と、メイン実装が適切でない場合に使用される代替実装です。







主な実装は、 Mockito.when



メソッドに基づいています。 このメソッドは、「パラメーター」としてモックオブジェクトの再定義されたメソッドへの呼び出し(このようにして検出されたアクションが修正されます)をOngoingStubbing



Mockito.then...



ファミリーのメソッドの1つを呼び出すことができるOngoingStubbing



型のオブジェクトを返します(これにより、この効果に対する反応が設定されます)。 すべてをまとめると、最も単純な場合、次のようになります。







 List<String> data = new ArrayList<>(); data.add("dataItem"); Mockito.when(dataService.getAllData()).thenReturn(data);
      
      





この操作の後、 getAllData()



オブジェクトでgetAllData()



メソッドをdataService



、リストの最初の行で指定されたオブジェクトを取得します。







ここでは、おなじみの「オブジェクト指向」の直観が何らかの誤動作を与える可能性があるため、これについて詳しく説明する価値があります。 Java構文の観点からすると、パラメーターとしてwhen



メソッドに渡される値は、もちろん、オーバーライドされたメソッドによって返される値です。 モックの場合、これは空の値です;スパイの場合、これは実際のオブジェクトのメソッドによって返される値です。 しかし、Mockitoの「フードの下」で動作する魔法のおかげで、 when



メソッドは、mock-objectメソッドの呼び出しがwhen



when



かっこ内にあるwhen



のみ正常に動作します(エラーで起動してもクラッシュしません)。







このようなイデオロギーは、Mockitoでモックの動作を定義するときによく機能します。(モックオブジェクトまたはMockito



クラスの)メソッドを呼び出すことにより、返される値を取得しようとはしませんが、何らかの方法で使用するモックオブジェクトのメソッドの呼び出しに影響を与えます:その境界、結果の設定、課題の観察などを確立します。 少し霧がかかっているように聞こえますが、最初の衝突では奇妙に見えますが、それを理解すると、すぐにこのアプローチがスタブを扱うという文脈で完全に自然であると感じるようになります。







条件と呼び出しの結果をリンクする別の実装は、 Mockito.do...



ファミリーのMockito.do...



。 これらのメソッドを使用すると、呼び出しの結果から始まる動作を設定し、 Stubber



クラスのオブジェクトを返すことができます。これにより、すでに条件を設定できます。 この方法で行われた上記と同じバインディングは、次のようになります。







 List<String> data = new ArrayList<>(); data.add("dataItem"); Mockito.doReturn(data).when(dataService).getData()
      
      





違いは何ですか、なぜMockito.when



を介してバインドするMockito.when



が望ましいと考えられ、まだMockito.do...



のメソッドを使用するMockito.do...



か? 注:最初の実装では、メソッドの動作(この場合はgetAllData()



)を設定するときに、まだ再定義されていないバージョンへの呼び出しが最初に実行され、その後のみMockitoの腸内でオーバーライドが発生します。 2番目のケースでは、このような呼び出しは発生しません。 Stubber.when



メソッドStubber.when



メソッドに直接渡され、このメソッドによって返されたメソッドから同じタイプの異なる性質のオブジェクトが返されます。 この違いがすべてを決定します。 Mockito.do...



を介したバインドは、コンパイル段階で、再定義可能なメソッドを呼び出したり、指定された戻り値と型の互換性があるかどうかをまったく制御しません。 したがって、通常はMockito.when



-これに間違いはありません。 しかし、オーバーライドされたメソッドの呼び出しを避けたい場合があります-新しく作成されたモックでは、そのような呼び出しはかなり受け入れられますが、すでにこのメソッドを再定義したり、スパイに対処している場合、それは望ましくない可能性があり、例外をスローしても必要な再定義は許可されません。 そして、ここでMockito.do...



を介したリンクがMockito.do...



になりMockito.do...









Mockito.do...



メソッドなしではできない別の状況は、 void



返すメソッドをオーバーライドすることです。保留中のMockito.when



パラメーターは、そのようなメソッドでは機能しません。 Mockito.doReturn



ここではMockito.doAnswer



していませんが、 Mockito.doThrow



Mockito.doAnswer



、およびめったに十分ではMockito.doNothing



ます。







次に、呼び出しの条件と結果を設定する方法についてもう少し詳しく検討します。 Mockito.when



を介したバインディングのみを検討します。代替方法は、処理がほぼ完全に類似しています。







通話条件を設定する



上記の例は、パラメーターのないメソッドに関するものであり、関連する呼び出し条件は、呼び出しの事実という1つの可能性があります。 パラメーターが表示されるとすぐに、状況はより複雑になります。 少なくとも、動作を設定しているメソッドを呼び出すには、何かを渡す必要があります。 しかし、もう1つ重要なことがあります。特定の要件を満たすパラメーターを使用して呼び出した場合にのみ、特定の反応が得られるとは限りません。 DataService



は次のメソッドがあります。







 String getDataItemById(String id) { // some code... }
      
      





引数に関係なくこのメソッドの呼び出しに応答を設定する必要がある場合、 Mockito.any



メソッドを使用する必要があります。







 Mockito.when(dataService.getDataItemById(any())) .thenReturn("dataItem");
      
      





引数の特定の値にのみ反応するためにモックが必要な場合は、この値を直接使用するか、 Mockito.eq



(同等の場合)またはMockito.same



(リンク比較が必要な場合)のメソッドを使用できます。







 Mockito.when(dataService.getDataItemById("idValue")) .thenReturn("dataItem"); // or Mockito.when(dataService.getDataItemById(Mockito.eq("idValue"))) .thenReturn("dataItem");
      
      





引数がいくつかの要件を満たすようにしたい場合、同じMockito



クラスの便利な特殊な静的メソッドがいくつかあります(たとえば、文字列は、特定の文字シーケンスの先頭または末尾、パターンマッチングなどでコンテンツをチェックできます)。 ArgumentMatcher機能インターフェイスの実装を受け入れる一般的なMockito.argThatメソッド(およびプリミティブ型の類似物)もあります。







 Mockito.when(dataService.getDataById( Mockito.argThat(arg -> arg == null || arg.length() > 5))) .thenReturn("dataItem");
      
      





ArgumentMatchers



クラスとAdditionalMatchers



クラスを使用すると、このインターフェイスの便利な市販の実装を使用できます。 たとえば、 AdditionalMatchers.or



AdditionalMatchers.and



使用すると、他のマッチャーを組み合わせることができます(注:これらのクラスの静的メソッドはマッチャーのインスタンスを返さず、アクセスするだけです!)







同じメソッドの場合、引数の要件が異なる動作を複数回設定でき、この方法で定義されたすべての動作モデルが同時に機能します。 もちろん、場合によっては交差することがあります。たとえば、パラメーターのint



値が5未満の場合は1つの結果を返し、偶数値を受け取った場合はもう1つの結果を返すように要求します。 この状況では、後で指定される動作が優先されます。 したがって、複雑な動作パターンを定義するときは、最も弱い要件(制限内-any any()



)から始めてから、より具体的な要件に進む必要があります。







複数の引数を持つメソッドを使用する場合、指定された要件は論理ANDに従って結合されます。つまり、指定された結果を取得するには、すべての引数が指定された要件を満たす必要があります。 それらを組み合わせるための任意の方法を設定する方法は見つかりませんでしたが、おそらく存在します。







さらに、このようなメソッドの動作を指定する場合、 Mockito



と値の直接転送をMockito



の静的メソッドを組み合わせることはできません。 Mockito.eq



またはMockito.same



使用します。







通話結果の設定



モックオブジェクトメソッドが呼び出された後、オブジェクトは呼び出しに応答する必要があります。 考えられる主な結果は、結果を返し、例外をスローすることです。Mockitoツールキットが主に設計されているのは、これらのオプションに正確に基づいています。







すでに上に示した最も単純なケースでは、呼び出しに対する応答は値を返すことです。 もう一度彼のコードをあげます。







 List<String> data = new ArrayList<>(); data.add("dataItem"); Mockito.when(dataService.getAllData()).thenReturn(data);
      
      





注:オブジェクトを返すことしかできず、プリミティブ用の個別のメソッドはありません。 したがって、メソッドがプリミティブ値を返す場合、そのような状況ではun / boxingが発生します。 ほとんどの場合、これは干渉しませんが、コンパイラがそうでないと考える場合は、何らかの形で彼に同意する必要があります...または彼の警告に我慢しなければなりません。







例外を投げることは難しくありません:







 Mockito.when(dataService.getDataById("invalidId")) .thenThrow(new IllegalArgumentException());
      
      





別の方法があります:例外オブジェクトを作成して直接スローするか、例外クラスのみをMockitoに提供して自動的に作成することができます:







 Mockito.when(dataService.getDataById("invalidId")) .thenThrow(IllegalArgumentException.class);
      
      





どちらの場合でも、構文では例外を使用して確認できますが、Mockitoでは、例外の種類がこの例外をスローする方法と一致しない場合、そのようなテストを実行できません。







クラスをパラメーターとして使用する場合、コンストラクター(パラメーターなしでも)とフィールドの直接初期化は無視されます-オブジェクトはそれらをバイパスして作成されるため(結局、これはMockitoです!)、スローされる例外のすべてのフィールドはnull



になりnull



。 したがって、例外の内容が重要な場合(たとえば、デフォルト値を持つtype



フィールドなど)、このメソッドを放棄して手動で例外を作成する必要があります。







これらの反応オプションは、特定の条件での呼び出しに応じて、常に特定の結果の同じ値を返す必要がある場合、または常に同じ例外をスローする必要がある場合に適しています。ほとんどの場合、これらの機能で十分です。 しかし、さらなる柔軟性が必要な場合はどうでしょうか? メソッドが値のコレクションを受け入れ、最初の1対1に関連付けられた値の別のコレクションを返し(たとえば、IDのセットによってデータオブジェクトのコレクションを取得する)、テストで異なる入力のセットでこのモックオブジェクトを繰り返し使用するとしますデータ、対応する結果を毎回取得します。 もちろん、特定のパラメーターセットそれぞれに対する反応を個別に記述することもできますが、 Mockito.thenAnswer



メソッド(別名Mockito.then



というより便利なソリューションがあります。 Answer



関数インターフェイスの実装を受け入れ、その唯一のメソッドはInvocationOnMock



クラスのオブジェクトを受け取ります。 後者からは、メソッド呼び出しのパラメーター(番号ごとに、または配列の形で一度に)を要求し、必要に応じてそれらを操作できます。 たとえば、私のコレクションの各要素の値に対応する値を取得し、それらから新しいコレクションを作成して返すことができます(注:期待どおりの結果はパラメーターオブジェクトの一部のフィールドには書き込まれず、単に返されます)。







 Mockito.when(dataService.getDataByIds(Mockito.any())) .thenAnswer(invocation -> invocation .<List<String>>getArgument(0).stream() .map(id -> { switch (id) { case "a": return "dataItemA"; case "b": return "dataItemB"; default: return null; } }) .collect(Collectors.toList()));
      
      





イデオロギー的に、これは実際のメソッドのモデルを書くようなものです:パラメータを取得し、処理し、結果を返します。 - , - , , , mock- .







Answer



, , — , AnswersWithDelay



, ReturnsElementsOf



. .







: InvocationOnMock



Object[]



, generic-.







thenCallRealMethod



. . mock-, spy-. mock , , - null



. spy thenCallRealMethod



spy ; , - .







thenAnswer



: InvocationOnMock



callRealMethod()



— , "" - .







OngoingStubbing



OngoingStubbing



, , , . , . thenReturn



thenThrow



, varargs. .







 Mockito.when(dataService.getDataById("a")) .thenReturn("valueA1", "valueA2") .thenThrow(IllegalArgumentException.class);
      
      





"valueA1



, — "valueA2



( ), ( ) IllegalArgumentException



.









: (mock' ), . , : , , . verify



.







, , :







 Mockito.verify(dataService).getDataById(Mockito.any());
      
      





, getDataById



, , . , Mockito, when



, , , mock-. , , , when



, — mock', (. ).







:







 Mockito.verify(dataService, Mockito.times(1)) .getDataById(Mockito.any());
      
      





Mockito.times



; Mockito.never



. Mockito.atLeast



( Mockito.atLeastOnce



1) Mockito.atMost



, , Mockito.only



, , mock- (. . ).







, Mockito



, VerificationAfterDelay



VerificationWithTimeout



, Mockito.after



Mockito.timeout



. 例:







 Mockito.verify(dataService, Mockito.after(1000).times(1)) .getDataById(Mockito.any());
      
      





, mock , , , . . after



timeout



, , , — , . , timeout



— . VerificationWithTimeout



never



atMost



: .







, Mockito.any()



. , , — Mockito , , . Mock- , , , , :







 dataService.getDataById("a"); dataService.getDataById("b"); Mockito.verify(dataService, Mockito.times(2)).getDataById(Mockito.any()); Mockito.verify(dataService, Mockito.times(1)).getDataById("a"); Mockito.verify(dataService, Mockito.never()).getDataById("c"); dataService.getDataById("c"); Mockito.verify(dataService, Mockito.times(1)).getDataById("c"); Mockito.verifyNoMoreInteractions(dataService);
      
      





verifyNoMoreInteractions



( verifyZeroInteractions



) — - ( verify



) mock- — . : varargs, , , !







, , , . , InOrder



:







 InOrder inOrder = Mockito.inOrder(dataService);
      
      





varargs; — mock- , InOrder



. verify



, Mockito.verify



:







 inOrder.verify(dataService, times(2)).saveData(any()); inOrder.verify(dataService).getData();
      
      





, saveData



, getData



. , InOrder



, — .







, , — , . - , , — , , . ArgumentCaptor



capture()



. 例:







 DataSearchRequest request = new DataSearchRequest("idValue", new Date(System.currentTimeMillis()), 50); dataService.getDataByRequest(request); ArgumentCaptor<DataSearchRequest> requestCaptor = ArgumentCaptor.forClass(DataSearchRequest.class); Mockito.verify(dataService, times(1)).getDataByRequest(requestCaptor.capture()); assertThat(requestCaptor.getAllValues()).hasSize(1); DataSearchRequest capturedArgument = requestCaptor.getValue(); assertThat(capturedArgument.getId()).isNotNull(); assertThat(capturedArgument.getId()).isEqualTo("idValue"); assertThat(capturedArgument.getUpdatedBefore()).isAfterYear(1970); assertThat(capturedArgument.getLength()).isBetween(0, 100);
      
      





ArgumentCaptor



, , ArgumentCaptor



. getValue()



, getAllValues()



— . , , .







Mock- Mockito



, mock- , — @Mock



- :







 MockitoAnnotations.initMocks(this);
      
      





( , mock', )







spy @Spy



@Mock



… spy , , ? , — spy .







@Captor



ArgumentCaptor



— , , .







@InjectMocks



. - Mockito, . mock- , . , . - , null



, - . ( ) dependency injection.







Mockito



, : mock (spy, argument captor...), , , . , mock' — , . JUnit , , TestNG — . , , mock' , , , . . , , — , .







, mock- . TestNG @BeforeMethod



( @AfterMethod



). mock' , , ( JUnit — @Before



).







, , — Mockito.reset



Mockito.clearInvocations



. varargs, mock'. , . : (, ) , / mock' , — . , mock' . . , , .







(, ) — MockitoAnnotations.initMocks(this);



。 "" , Mockito.







— Mockito. . mock- , ( mock' ). , MockitoSession



, . TestNG:







 @Mock DataService dataService; MockitoSession session; @BeforeMethod public void beforeMethod() { session = Mockito.mockitoSession() .initMocks(this) .startMocking(); } @Test public void testMethod() { // some code using the dataService field } @AfterMethod public void afterMethod() { session.finishMocking(); }
      
      





, — , "" (, ) , .







他に何?



Mockito: mock spy-, . , . , , :









Mockito . javadoc' Mockito



.







, , .








All Articles