SoftwareEngineering/ProgramLanguage/Java/UnitTest/JMockit

JMockit Java用の自動テストツールキット

モッキング

  1. 模範型とインスタンス
  2. 期待
  3. 記録再生検証モデル
  4. テストされたクラスのインスタンス化と注入
  5. 期待通りの結果を記録する
  6. 引数値の柔軟な照合
    1. 「任意の」フィールドを使用する
    2. "with"メソッドの使用
  7. 呼び出しカウント制約の指定
  8. 明示的な検証
    1. 注文の確認
  9. 代理人:カスタム結果を指定する
  10. 検証のための呼び出し引数の取得
    1. 1回の呼び出しから引数を取り込む
    2. 複数の呼び出しから引数を取り込む
    3. 新しいインスタンスの取得
  11. カスケーディングモック
    1. カスケード静的ファクトリメソッド
    2. カスケード自己復帰方法
  12. 特定のインスタンスへの呼び出しの照合
    1. 注入可能な模擬インスタンス
    2. 複数の模擬インスタンスの宣言
    3. 指定されたコンストラクタで作成されたインスタンス
  13. 部分的な嘲笑
  14. 不特定の実装クラスを模擬する
  15. 完全な検証とその他のバリエーション
    1. 完全な検証
    2. 呼び出しが起こらなかったことを確認する
    3. モックされたタイプのセットを完全に検証するように制限する
    4. 呼び出しが発生していないことを確認する
    5. 発生してはならない不特定の呼び出しを確認する

JMockitライブラリでは、 Expectations APIは自動化された開発者テストでのモック使用を豊富にサポートしています。 模擬を使用する場合、テストはテスト対象のコードの動作に焦点を当てます。 テストするコードは、依存する他のタイプとの相互作用によって表現されます。 モッキングは通常、単体テストの構築に使用されます。 単体テストは、テスト対象のユニットが依存する他のユニットの実装から分離して実行されます。 通常、振る舞いの単位は単一のクラスですが、単体テストの目的で強く関連するクラス全体を単一の単位として考えることもできます(通常は、またはそれ以上のヘルパークラス、おそらくpackage-private)。 一般的には、個々の方法はそれ自体で別個の単位と見なすべきではない。

しかし、 厳密なユニットテストは推奨される方法ではありません。 すべての単一の依存関係をモックしようとすべきではありません。 モッキングは適度に使用するのが一番です。 可能であれば、分離された単体テストよりも統合テストを優先してください。 これは、時には、特定の依存関係が実際の実装を簡単に使用できない統合テストの作成や、うまく配置された相互作用がテストを大幅に促進するコーナーケースのテストを作成しようとするときにも役立ちます。

模範型とインスタンス

Mockingは、テストされるクラスをその依存関係から隔離するためのメカニズムを提供します。 私たちは、テストクラス内の適切な模擬フィールドやモックパラメータを宣言することによって、特定の依存関係を嘲笑することを指定します。 疑似フィールドはテストクラスの注釈付きインスタンスフィールドとして宣言され、モックパラメータはテストメソッドの注釈付きパラメータとして宣言されます。 模擬フィールドまたはパラメータのタイプは、 interface 、 class ( abstractものとfinalなものを含む)、注釈、またはenum型のいずれの種類の参照タイプであってもよい。

次のサンプルのテスト・スケルトン(Java 8とJUnit 4を使用)は、模擬フィールドとモック・パラメータの宣言、およびテスト・コードで一般的に使用される方法の基本的な図として機能します。

@Mocked Dependency mockInstance; // holds a mocked instance automatically created for use in each test

@Test
public void doBusinessOperationXyz(@Mocked AnotherDependency anotherMock) {
   ...
   new Expectations() {{ // an "expectation block"
      ...
      // Record an expectation, with a given value to be returned:
      mockInstance.mockedMethod(...); result = 123;
      ...
   }};
   ...
   // Call the code under test.
   ...
   new Verifications() {{ // a "verification block"
      // Verifies an expected invocation:
      anotherMock.save(any); times = 1;
   }};
   ...
}

テストメソッドで宣言されたモックパラメータの場合、宣言された型のインスタンスはJMockitによって自動的に作成され、テストメソッドを実行するときにJUnit / TestNGテストランナーによって渡されます。 したがって、パラメータ値は決してnullはなりません。 擬似フィールドの場合、宣言された型のインスタンスが自動的に作成され、フィールドに割り当てられます( finalない場合)。

モックフィールドとパラメータを宣言するときに使用できる3つの異なるモッキングアノテーションがあります。 モックされたクラスの既存のインスタンスと将来のインスタンスのすべてのメソッドとコンストラクタをモックする@Mocked (それを使用するテストの期間中)。 @Injectable:単一の模擬インスタンスのインスタンスメソッドにモックを制限する。 モックされたインタフェースを実装しているクラス、または擬似クラスを拡張しているサブクラスに拡張する@Capturing 。

JMockitによって作成された模擬インスタンスは、テストコード(期待値の記録と検証用)やテスト中のコードに渡すことができます。 あるいは、単に使用されないかもしれません。 他のモッキングAPIとは異なり、これらのモックされたオブジェクトは、依存関係にあるインスタンスメソッドを呼び出すときに、テスト対象のコードで使用されるものである必要はありません。 @Mockedまたは@Capturingを使用する場合( @Mockedを使用しない場合)、JMockitは、模擬されたインスタンスメソッドが呼び出される特定のオブジェクトを気にしません。 これにより、 newオペレータを使用してnewインスタンスのコンストラクタを呼び出すときに、テスト対象のコードの内部に直接作成されたインスタンスを透過的に模倣することができます。 インスタンス化されたクラスは、テストコードで宣言された疑似型でカバーする必要があります。

期待

期待値は、特定のテストに関連する特定の模擬メソッド/コンストラクタに対する一連の呼び出しを表します。 期待は同じメソッドまたはコンストラクタに対する複数の異なる呼び出しをカバーするかもしれませんが、テストの実行中に発生するそのような呼び出しをすべてカバーする必要はありません。 特定の呼び出しが所定の期待値と一致するかどうかは、メソッド/コンストラクタシグネチャだけでなく、メソッドが呼び出されたインスタンス、引数値、および/またはすでに一致した呼び出しの数などの実行時のアスペクトにも依存します。 したがって、いくつかのタイプのマッチング制約は、(任意に)所与の期待に対して指定することができる。

1つ以上の呼び出しパラメーターが関係する場合、各パラメーターに正確な引数値を指定することができます。 たとえば、 Stringパラメータに"test string"という値を指定すると、対応するパラメータでこの正確な値を持つ呼び出しのみが一致することになります。 後で説明するように、正確な引数値を指定する代わりに、異なる引数値のセット全体に一致するより緩やかな制約を指定することができます。

次の例は、 Dependency#someMethod(int, String)期待値を示しています。 このメソッドは、このメソッドへの呼び出しと、指定されたとおりの正確な引数値を照合します。 期待自体は、嘲笑されたメソッドへの独立した呼び出しによって指定されることに注意してください。 他のモッキングAPIでよく見られるように、特別なAPIメソッドは含まれていません。 ただし、この呼び出しは、テストに関心のある「実際の」呼び出しの1つとしてカウントされません。 それは、その期待が特定できるようにしかそこにありません。

@Test
public void doBusinessOperationXyz(@Mocked Dependency mockInstance) {
   ...
   new Expectations() {{
      ...
      // An expectation for an instance method:
      mockInstance.someMethod(1, "test"); result = "mocked";
      ...
   }};

   // A call to code under test occurs here, leading to mock invocations
   // that may or may not match specified expectations.
}

呼び出しの記録 、 再生 、および検証の違いを理解した後で、後で期待についてもっと詳しく見ていきます。

記録再生検証モデル

開発者テストは、少なくとも3つの別々の実行段階に分けることができます。 以下に示すように、フェーズは1つずつ順番に実行されます。

@Test
public void someTestMethod() {
   // 1. Preparation: whatever is required before the code under test can be exercised.
   ...
   // 2. The code under test is exercised, usually by calling a public method.
   ...
   // 3. Verification: whatever needs to be checked to make sure the code exercised by
   //    the test did its job.
   ...
}

最初に、テストに必要なオブジェクトとデータ項目を作成または取得する準備段階があります。 次に、テスト対象のコードが実行されます。 最後に、テストされたコードを実行した結果と期待される結果を比較します。

この3つのフェーズのモデルは、 アレンジ、アクト、アサートの構文 、つまり「AAA」とも呼ばれます 。 異なる言葉ですが、意味は同じです。

モックされたタイプ(およびモックされたインスタンス)を使用したビヘイビアベースのテストでは、前述の3つの従来のテストフェーズに直接関連する次の代替フェーズを特定できます。

呼び出しが記録されるレコードフェーズ。 これは、テスト準備中に、テストしたい呼び出しが実行される前に発生します。

テスト中のコードが実行されるとき、関心の模擬呼び出しが実行される再生フェーズ。 以前に記録された擬似メソッド/コンストラクタへの呼び出しが再生されるようになりました。 しかし、記録された呼び出しと再生された呼び出しとの間に1対1のマッピングはしばしばありません。

検証フェーズ。 この間、呼び出しは期待どおりに行われたことを確認できます。 これは、テスト中の呼び出しが実行された後で 、テスト検証中に発生します。

JMockitで記述された動作ベースのテストは、通常、次のテンプレートに適合します。

import mockit.*;
... other imports ...

public class SomeTest
{
   // Zero or more "mock fields" common to all test methods in the class:
   @Mocked Collaborator mockCollaborator;
   @Mocked AnotherDependency anotherDependency;
   ...

   @Test
   public void testWithRecordAndReplayOnly(mock parameters) {
      // Preparation code not specific to JMockit, if any.

      new Expectations() {{ // an "expectation block"
         // One or more invocations to mocked types, causing expectations to be recorded.
         // Invocations to non-mocked types are also allowed anywhere inside this block
         // (though not recommended).
      }};

      // Code under test is exercised.

      // Verification code (JUnit/TestNG assertions), if any.
   }

   @Test
   public void testWithReplayAndVerifyOnly(mock parameters) {
      // Preparation code not specific to JMockit, if any.

      // Code under test is exercised.

      new Verifications() {{ // a "verification block"
         // One or more invocations to mocked types, causing expectations to be verified.
         // Invocations to non-mocked types are also allowed anywhere inside this block
         // (though not recommended).
      }};

      // Additional verification code, if any, either here or before the verification block.
   }

   @Test
   public void testWithBothRecordAndVerify(mock parameters) {
      // Preparation code not specific to JMockit, if any.

      new Expectations() {{
         // One or more invocations to mocked types, causing expectations to be recorded.
      }};

      // Code under test is exercised.

      new VerificationsInOrder() {{ // an ordered verification block
         // One or more invocations to mocked types, causing expectations to be verified
         // in the specified order.
      }};

      // Additional verification code, if any, either here or before the verification block.
   }
}

上記のテンプレートには他のバリエーションもありますが、本質的には、検証ブロックが検証フェーズに属している間に、期待ブロックが記録フェーズに属し、テスト対象コードが実行される前に来ることが重要です。 テストメソッドには、noneを含む任意の数のexpectationブロックを含めることができます。 検証ブロックでも同様です。

匿名の内部クラスを使用してコードブロックを区切ることにより、現代のJava IDEで利用可能な「コード折りたたみ」機能を利用することができます。 次の図は、IntelliJ IDEAの外観を示しています。 code-folding.png

テストされたクラスのインスタンス化と注入

テスト・クラスの@Testedとして注釈された非finalインスタンス・フィールドは、テスト・メソッドの実行の直前に、自動インスタンス化および注入のために考慮されます。 現時点でフィールドにnull参照が保持されている場合は、テスト対象クラスの適切なコンストラクタを使用してインスタンスを作成し、内部依存関係が適切に注入されるようにします(該当する場合)。

@Injectableインスタンスをテスト対象オブジェクトに挿入するには、テストクラスに、 @Injectableと宣言された1つまたは複数の模擬フィールドまたはモックパラメータも含める必要があります。 @Mockedまたは@Capturingのみ注釈された擬似フィールド/パラメータは、注入の@Capturingはなりません 。 一方、注射可能なフィールド/パラメータのすべてが模擬可能な型を持つ必要はありません。 プリミティブ型または配列型を持つこともできます。 次のテストクラスの例を示します。

public class SomeTest
{
   @Tested CodeUnderTest tested;
   @Injectable Dependency dep1;
   @Injectable AnotherDependency dep2;
   @Injectable int someIntegralProperty = 123;

   @Test
   public void someTestMethod(@Injectable("true") boolean flag, @Injectable("Mary") String name) {
      // Record expectations on mocked types, if needed.

      tested.exerciseCodeUnderTest();

      // Verify expectations on mocked types, if required.
   }
}

non-mockable注入可能なフィールド/パラメータには明示的に指定された値が必要です。 そうでない場合はデフォルト値が使用されます。 注射可能なフィールドの場合、その値を単にフィールドに割り当てることができます。 代わりに、注入可能なテストメソッドパラメータの場合に値を指定する唯一の方法である@Injectable " value "属性で提供することもできます。

2種類の注入がサポートされています: コンストラクタ注入とフィールド注入。 最初のケースでは、テストされたクラスには、テストクラスで使用可能な注入可能な値および/またはテストされた値によって満たすことができるコンストラクタが必要です。 与えられたテストでは、使用可能な注入可能/検査された値のセットは、検査メソッドのインスタンスフィールドとして宣言された注入可能/検査済みフィールドのセットと、検査メソッドで宣言された注入可能/ したがって、同じテストクラスの異なるテストでは、同じテスト対象オブジェクトに注入する異なる値のセットを提供できます。

テストされたクラスが選択されたコンストラクタで初期化されると、残りの初期化されていない非finalインスタンスフィールドが注入のために考慮されます。 そのようなフィールドが注入されるたびに、同じ種類の注射可能/検査されたフィールドが検査クラス内で検索される。 1つだけが見つかった場合、その現在の値が読み込まれ、注入されたフィールドに格納されます。 複数ある場合は、注入されたフィールド名を使用して、同じタイプの注入可能/検査済みフィールドの中から選択します。

期待通りの結果を記録する

非voidの戻り値の型を持つ所定のメソッドの場合、 結果フィールドへの代入によって戻り値を記録できます。 再生フェーズでメソッドが呼び出されると、指定された戻り値が呼び出し側に返されます。 resultへの割り当ては、期待値ブロック内の記録された期待値を識別する呼び出しの直後に現れなければならない。

メソッドが呼び出されたときに例外またはエラーをスローする必要がある場合は、 resultフィールドを引き続き使用できます。 目的のスロー可能なインスタンスを単純に割り当てます。 スローされる例外/エラーの記録は、模造されたメソッド(任意の戻り型の)とモックされたコンストラクタに適用可能であることに注意してください。

resultフィールドを複数回連続して割り当てるだけで、複数の連続した結果(返す値および/または投げ捨てる値)を同じ期待値に対して記録することができます。 スローされる複数の戻り値および/または例外/エラーの記録は、同じ期待に対して自由に混合することができます。 与えられた期待に対して複数の連続した戻り値を記録する場合は、 returns(...)メソッドを1回呼び出すことができます。 また、 resultフィールドへの単一の代入は、それに割り当てられた値が連続する値を含むリストまたは配列である場合、同じ効果を達成します。

次のサンプルのテストでは、 ClassUnderTestから呼び出されたときに使用される、 ClassUnderTestされたDependencyAbcクラスのメソッドの両方のタイプの結果が記録されています。 テスト中のクラスの実装は次のようになります:

public class ClassUnderTest
{
(1)private final DependencyAbc abc = new DependencyAbc();

   public void doSomething() {
(2)   int n = abc.intReturningMethod();

      for (int i = 0; i < n; i++) {
         String s;

         try {
(3)         s = abc.stringReturningMethod();
         }
         catch (SomeCheckedException e) {
            // somehow handle the exception
         }

         // do some other stuff
      }
   }
}

SomeCheckedException doSomething()メソッドの可能性のあるテストでは、成功した任意の反復回数の後にSomeCheckedExceptionがスローされるケースをSomeCheckedExceptionできます。 これらの2つのクラス間の相互作用に関する一連の期待を記録したいと仮定して、以下のテストを書くかもしれません。

@Tested ClassUnderTest cut;

@Test
public void doSomethingHandlesSomeCheckedException(@Mocked DependencyAbc abc) throws Exception {
   new Expectations() {{
(1)   abc.intReturningMethod(); result = 3;

(2)   abc.stringReturningMethod();
      returns("str1", "str2");
      result = new SomeCheckedException();
   }};

   cut.doSomething();
}

このテストは、2つの異なる期待を記録します。 最初のintReturningMethod()は、 intReturningMethod()は呼び出されたときに3を返します。 2番目のメソッドはstringReturningMethod()の3つの連続した結果のシーケンスを指定します。 最後の結果は目的の例外のインスタンスになります(例外が伝播されない場合にのみ渡されます) 。

引数値の柔軟な照合

記録フェーズと検証フェーズの両方で、モックされたメソッドまたはコンストラクタへの呼び出しによって期待値が識別されます。 メソッド/コンストラクタに1つ以上のパラメータがある場合、 doSomething(1, "s", true);ような記録/検証された期待値doSomething(1, "s", true); 引数の値が等しい場合は、 再生フェーズでの呼び出しと一致します。 引数が通常のオブジェクト(プリミティブや配列ではない)の場合、 equals(Object)メソッドが等価チェックに使用されます。 配列型のパラメータの場合、等価チェックは個々の要素に拡張されます。 したがって、各次元において同じ長さを有し、対応する要素が等しい2つの異なる配列インスタンスが等しいとみなされる。

与えられたテストでは、それらの引数値が正確に何であるかを正確には知らない場合があります。 あるいは、単にテストされているものにとって必須ではありません。 したがって、記録された、または検証された呼び出しが、異なる引数値を持つ再生された呼び出し全体と一致するようにするために、実際の引数値の代わりに柔軟な引数一致制約を指定できます。 これは、 anyXyz フィールドおよび/またはwithXyz(...)メソッドを使用してanyXyz ます 。 "any"フィールドと "with"メソッドはすべて、 mockit.Invocationsで定義されていmockit.Invocations 。 これは、テストで使用されるすべての期待値/検証クラスの基本クラスです。 したがって、それらは期待だけでなく検証ブロックでも使用できます。

「任意の」フィールドを使用する

最も一般的な引数マッチング制約は、与えられたパラメータ(適切なパラメータ型のもの)の任意の値で呼び出しを一致させることの最も制限の少ないものです。 このような場合には、各プリミティブ型(および対応するラッパークラス)ごとに1つずつ、文字列に1つ、 Object型の「汎用」に、特別な引数一致フィールドのセット全体があります 。 以下のテストでは、いくつかの用途を示しています。

@Tested CodeUnderTest cut;

@Test
public void someTestMethod(@Mocked DependencyAbc abc) {
   DataItem item = new DataItem(...);

   new Expectations() {{
      // Will match "voidMethod(String, List)" invocations where the first argument is
      // any string and the second any list.
      abc.voidMethod(anyString, (List<?>) any);
   }};

   cut.doSomething(item);

   new Verifications() {{
      // Matches invocations to the specified method with any value of type long or Long.
      abc.anotherVoidMethod(anyLong);
   }};
}

" any "フィールドの使用は、呼び出し文の実際の引数位置に現れる必要があります。 ただし、同じ呼び出しで他のパラメーターの引き数を引き続き使用することはできます。 詳細については、 APIのドキュメントを参照してください。

"with"メソッドの使用

期待を記録または検証する際、呼び出しで渡された引数のサブセットに対して、 withXyz(...)メソッドへの呼び出しが発生する可能性があります。 それらは、(引数値、ローカル変数などを使用して)通常の引数渡しと自由に混在させることができます。 唯一の要件は、そのような呼び出しが、その前にではなく、記録された/検証された呼び出しステートメントの内部に現れることです。 たとえば、最初にwithNotEqual(val)呼び出し結果をローカル変数に代入してから、その変数を呼び出しステートメントで使用することはできません。 いくつかの "with"メソッドを使ったテストの例を以下に示します。

@Test
public void someTestMethod(@Mocked DependencyAbc abc) {
   DataItem item = new DataItem(...);

   new Expectations() {{
      // Will match "voidMethod(String, List)" invocations with the first argument
      // equal to "str" and the second not null.
      abc.voidMethod("str", (List<?>) withNotNull());

      // Will match invocations to DependencyAbc#stringReturningMethod(DataItem, String)
      // with the first argument pointing to "item" and the second one containing "xyz".
      abc.stringReturningMethod(withSameInstance(item), withSubstring("xyz"));
   }};

   cut.doSomething(item);

   new Verifications() {{
      // Matches invocations to the specified method with any long-valued argument.
      abc.anotherVoidMethod(withAny(1L));
   }};
}

上に示したものよりも多くの "with"メソッドがあります。 詳細については、 APIドキュメントを参照してください。

JMockitでは、APIで使用可能ないくつかの定義済みの引数マッチング制約に加えて、 with(Delegate )メソッドとwithArgThat(Matcher)メソッドを使用してカスタム制約を提供できます。

呼び出しカウント制約の指定

これまでのところ、関連するメソッドやコンストラクタ以外にも、呼び出し結果と引数マッチャーがあることがわかりました。 テスト対象のコードが同じメソッドまたはコンストラクタを異なる引数または同じ引数で複数回呼び出すことができる場合、これらの個別の呼び出しをすべて考慮する方法が必要になることがあります。

所与の期待値と一致すると予想されるおよび/または許可される呼び出しの数は、 呼び出しカウント制約によって指定することができる。 モッキングAPIには、 times 、 minTimes 、およびmaxTimesの 3つの特殊フィールドがあります 。 これらのフィールドは、録音時または期待の検証時に使用できます。 いずれの場合も、期待値に関連付けられたメソッドまたはコンストラクタは、指定された範囲内にあるいくつかの呼び出しを受け取るように制約されます。 予想される下限値または上限値より少ないかまたは多い呼び出し、およびテスト実行は自動的に失敗します。 いくつかのサンプルテストを見てみましょう。

@Tested CodeUnderTest cut;

@Test
public void someTestMethod(@Mocked DependencyAbc abc) {
   new Expectations() {{
      // By default, at least one invocation is expected, i.e. "minTimes = 1":
      new DependencyAbc();

      // At least two invocations are expected:
      abc.voidMethod(); minTimes = 2;

      // 1 to 5 invocations are expected:
      abc.stringReturningMethod(); minTimes = 1; maxTimes = 5;
   }};

   cut.doSomething();
}

@Test
public void someOtherTestMethod(@Mocked DependencyAbc abc) {
   cut.doSomething();

   new Verifications() {{
      // Verifies that zero or one invocations occurred, with the specified argument value:
      abc.anotherVoidMethod(3); maxTimes = 1;

      // Verifies the occurrence of at least one invocation with the specified arguments:
      DependencyAbc.someStaticMethod("test", false); // "minTimes = 1" is implied
   }};
}

resultフィールドとは異なり、これら3つのフィールドのそれぞれは、所与の期待に対して最大で1回指定することができます。 負でない整数値は、呼び出しカウント制約のいずれに対しても有効です。 times = 0またはmaxTimes = 0が指定されている場合、リプレイ中に発生する期待値に一致する最初の呼び出しがあれば、テストは失敗します。

明示的な検証

記録された期待値に対して呼び出し回数の制約を指定することに加えて、テスト中のコードを呼び出した後 、 検証ブロック内で一致する呼び出しを明示的に検証することもできます。

「 new Verifications () {...} 」ブロック内では、戻り値を記録するために使用されるメソッドとフィールドを除いて、「 new Expectations() {...} 」ブロックで使用可能な同じAPIを使用できますnew Expectations() {...}スローされた例外/エラー。 つまり、 anyXyzフィールド、 withXyz(...)引数一致メソッド、 times 、 minTimes 、およびmaxTimes呼び出しカウント制約フィールドを自由に使用できます。 テストの例を次に示します。

@Test
public void verifyInvocationsExplicitlyAtEndOfTest(@Mocked Dependency mock) {
   // Nothing recorded here, though it could be.

   // Inside tested code:
   Dependency dependency = new Dependency();
   dependency.doSomething(123, true, "abc-xyz");

   // Verifies that Dependency#doSomething(int, boolean, String) was called at least once,
   // with arguments that obey the specified constraints:
   new Verifications() {{ mock.doSomething(anyInt, true, withPrefix("abc")); }};
}

デフォルトでは、検証では、 少なくとも1つの一致する呼び出しが再生中に発生したことが確認されます。 呼び出しの正確な数( 1を含む)を確認する必要がある場合、 times = n制約を指定する必要があります。

注文の確認

Verificationsクラスを使用して作成された正規の検証ブロックは順序付けされていません。 再生フェーズ中にaMethod()とanotherMethod()が呼び出された実際の相対的な順序は検証されませんが、各メソッドが少なくとも1回は実行されたことのみが確認されます。 呼び出しの相対的な順序を確認する場合は、代わりに「 new VerificationsInOrder () {...} 」ブロックを使用する必要があります。 このブロック内では、呼び出しが発生したと思われる順番で、1つ以上の模擬型に呼び出しを書き込むだけです。

@Test
public void verifyingExpectationsInOrder(@Mocked DependencyAbc abc) {
   // Somewhere inside the tested code:
   abc.aMethod();
   abc.doSomething("blah", 123);
   abc.anotherMethod(5);
   ...

   new VerificationsInOrder() {{
      // The order of these invocations must be the same as the order
      // of occurrence during replay of the matching invocations.
      abc.aMethod();
      abc.anotherMethod(anyInt);
   }};
}

abc.doSomething(...)呼び出しはテストで検証されなかったため、いつでも(またはまったく)発生していない可能性があります。

代理人:カスタム結果を指定する

resultフィールドの代入やreturns(...)メソッドの呼び出しによって、呼び出しの結果を記録する方法を見てきました。 呼び出し引数をwithXyz(...)メソッドのグループとさまざまなanyXyzフィールドと柔軟に対応させる方法も見てきました。 しかし、再生時に受け取る引数に基づいて、記録された呼び出しの結果をテストで判断する必要がある場合はどうなりますか? 以下に例示されているように、 Delegateのインスタンスを通じて実行できます。

@Tested CodeUnderTest cut;

@Test
public void delegatingInvocationsToACustomDelegate(@Mocked DependencyAbc anyAbc) {
   new Expectations() {{
      anyAbc.intReturningMethod(anyInt, anyString);
      result = new Delegate() {
         int aDelegateMethod(int i, String s) {
            return i == 1 ? i : s.length();
         }
      };
   }};

   // Calls to "intReturningMethod(int, String)" will execute the delegate method above.
   cut.doSomething();
}

Delegateインターフェースは空です。 単に、再生時の実際の呼び出しを、割り当てられたオブジェクトの「委譲」メソッドに委譲する必要があることをJMockitに伝えるために使用されます。 このメソッドは、デリゲートオブジェクト内の唯一の非privateメソッドであれば、任意の名前を付けることができます。 デリゲートメソッドのパラメータは、記録されたメソッドのパラメータと一致するか、存在しないようにする必要があります。 いずれの場合でも、デリゲートメソッドは、最初のパラメータとしてInvocation型の追加パラメータを持つことができます。 再生中に受け取られたInvocationオブジェクトは、呼び出されたインスタンスと実際の呼び出し引数にアクセスし、他の機能も提供します。 デリゲートメソッドの戻り値の型は、記録されたメソッドと同じである必要はありませんが、後でClassCastExceptionを回避するために互換性がある必要がありClassCastException 。

コンストラクターはデリゲートメソッドでも処理できます 。 次のサンプル・テストは、例外を条件付きでスローするメソッドに委譲されるコンストラクタ呼び出しを示しています。

@Test
public void delegatingConstructorInvocations(@Mocked Collaborator anyCollaboratorInstance) {
   new Expectations() {{
      new Collaborator(anyInt);
      result = new Delegate() {
         void delegate(int i) { if (i < 1) throw new IllegalArgumentException(); }
      };
   }};

   // The first instantiation using "Collaborator(int)" will execute the delegate above.
   new Collaborator(4);
}

検証のための呼び出し引数の取得

呼び出し引数は、特別な " withCapture(...) "メソッドを使用して後で確認するためにキャプチャすることができます。 3つの異なるケースがあり、それぞれ固有のキャプチャ方法があります。

一度の呼び出しで、 T withCapture()メソッドに渡される引数の検証: T withCapture() ; モックされたメソッドに渡された引数の検証、複数の呼び出し: T withCapture(List<T>) ; List<T> withCapture(T)コンストラクタに渡される引数の検証: List<T> withCapture(T) 。

1回の呼び出しから引数を取り込む

単一の呼び出しから擬似メソッドまたはコンストラクタへの引数を取得するには、 withCapture()を使用します。 これは、次のテストの例で示すとおりです。

@Test
public void capturingArgumentsFromSingleInvocation(@Mocked Collaborator mock) {
   // Inside tested code:
   ...
   new Collaborator().doSomething(0.5, new int[2], "test");

   // Back in test code:
   new Verifications() {{
      double d;
      String s;
      mock.doSomething(d = withCapture(), null, s = withCapture());

      assertTrue(d > 0.0);
      assertTrue(s.length() > 1);
   }};
}

withCapture()メソッドは、検証ブロックでのみ使用できます。 通常は、一致する単一の呼び出しが発生すると予想される場合に使用します。 しかし、そのような呼び出しが複数発生した場合は、最後に発生した呼び出しが前の呼び出しによって取り込まれた値を上書きします。 これは、複合型(JPA @Entityと考える)のパラメータで特に有用です。 このパラメータには、値をチェックする必要があるいくつかの項目が含まれている場合があります。

複数の呼び出しから引数を取り込む

疑似メソッドまたはコンストラクタへの複数の呼び出しが必要な場合、それらのすべての値を取得する場合は、下の例のようにwithCapture(List)メソッドを代わりに使用する必要があります。

@Test
public void capturingArgumentsFromMultipleInvocations(@Mocked Collaborator mock) {
   // Inside tested code:
   mock.doSomething(dataObject1);
   mock.doSomething(dataObject2);
   ...

   // Back in test code:
   new Verifications() {{
      List<DataObject> dataObjects = new ArrayList<>();
      mock.doSomething(withCapture(dataObjects));

      assertEquals(2, dataObjects.size());
      DataObject data1 = dataObjects.get(0);
      DataObject data2 = dataObjects.get(1);
      // Perform arbitrary assertions on data1 and data2.
   }};
}

withCapture()とはwithCapture(List) 、 withCapture(List)オーバーロードは期待記録ブロックでも使用できます。

新しいインスタンスの取得

最後に、テスト中に作成された模擬クラスの新しいインスタンスを取得できます。

@Test
public void capturingNewInstances(@Mocked Person mockedPerson) {
   // From the code under test:
   dao.create(new Person("Paul", 10));
   dao.create(new Person("Mary", 15));
   dao.create(new Person("Joe", 20));
   ...

   // Back in test code:
   new Verifications() {{
      // Captures the new instances created with a specific constructor.
      List<Person> personsInstantiated = withCapture(new Person(anyString, anyInt));

      // Now captures the instances of the same type passed to a method.
      List<Person> personsCreated = new ArrayList<>();
      dao.create(withCapture(personsCreated));

      // Finally, verifies both lists are the same.
      assertEquals(personsInstantiated, personsCreated);
   }};
}

カスケーディングモック

機能が多数の異なるオブジェクトに分散されている複雑なAPIを使用する場合、 obj1.getObj2(...).getYetAnotherObj().doSomething(...)という形式の連鎖呼び出しを見るのは珍しいことではありません。 そのような場合、 obj1から始まるチェーン内のすべてのオブジェクト/クラスをモックする必要があるかもしれません。

この3つの注釈注釈はすべてこの機能を提供します。 次のテストでは、 java.netおよびjava.nio APIを使用した基本的な例を示します。

@Test
public void recordAndVerifyExpectationsOnCascadedMocks(
   @Mocked Socket anySocket, // will match any new Socket object created during the test
   @Mocked SocketChannel cascadedChannel // will match cascaded instances
) throws Exception {
   new Expectations() {{
      // Calls to Socket#getChannel() will automatically return a cascaded SocketChannel;
      // such an instance will be the same as the second mock parameter, allowing us to
      // use it for expectations that will match all cascaded channel instances:
      cascadedChannel.isConnected(); result = false;
   }};

   // Inside production code:
   Socket sk = new Socket(); // mocked as "anySocket"
   SocketChannel ch = sk.getChannel(); // mocked as "cascadedChannel"

   if (!ch.isConnected()) {
      SocketAddress sa = new InetSocketAddress("remoteHost", 123);
      ch.connect(sa);
   }

   InetAddress adr1 = sk.getInetAddress();  // returns a newly created InetAddress instance
   InetAddress adr2 = sk.getLocalAddress(); // returns another new instance
   ...

   // Back in test code:
   new Verifications() {{ cascadedChannel.connect((SocketAddress) withNotNull()); }};
}

上記のテストでは、モックされたSocketクラスの適格メソッドへの呼び出しは、テスト中に発生するたびにカスケードされたモックオブジェクトを返します。 カスケードされたモックはさらにカスケードすることができるので、オブジェクト参照を返すメソッドからnull参照を取得することは決してありません(非適格な戻り値の型Objectまたはnullを返すnullを返すか、非模倣の空のコレクションを返すコレクション型)。

模擬フィールド/パラメータ(上記のcascadedChannelなど)から利用可能な模擬インスタンスがない限り、 cascadedChannelれた新しいインスタンスが最初に呼び出された模擬メソッドから作成されます。 上記の例では、同じInetAddressリターンタイプを持つ2つの異なるメソッドが異なるカスケードインスタンスを作成して返します。 同じメソッドは常に同じカスケードされたインスタンスを返します。

テスト中に存在する可能性がある同じタイプの他のインスタンスに影響を及ぼさないように、新しいカスケードインスタンスが@Injectableセマンティクスで作成されます。

最後に、必要であれば、カスケードされたインスタンスを非モックされたインスタンスに置き換えたり、模擬されたインスタンスを別のものに置き換えたり、まったく返さないようにすることは重要です。 そのためには、 resultフィールドに返される希望のインスタンスを割り当てる期待値を記録するか、そのようなインスタンスが必要ない場合はnullしnull 。

カスケード静的ファクトリメソッド

カスケードは、模擬クラスにstatic ファクトリメソッドが含まれてstaticシナリオでは非常に便利です。 次のサンプル・テストでは、JSF(Java EE)からjavax.faces.context.FacesContextクラスをモックするとします。

@Test
public void postErrorMessageToUIForInvalidInputFields(@Mocked FacesContext jsf) {
   // Set up invalid inputs, somehow.

   // Code under test which validates input fields from a JSF page, adding
   // error messages to the JSF context in case of validation failures.
   FacesContext ctx = FacesContext.getCurrentInstance();

   if (some input is invalid) {
      ctx.addMessage(null, new FacesMessage("Input xyz is invalid: blah blah..."));
   }
   ...

   // Test code: verify appropriate error message was added to context.
   new Verifications() {{
      FacesMessage msg;
      jsf.addMessage(null, msg = withCapture());
      assertTrue(msg.getSummary().contains("blah blah"));
   }};
}

上記のテストで興味深いのは、嘲笑されたインスタンスが自動的に返さFacesContext.getCurrentInstance()れるように、心配する必要がないということjsfです。

カスケード自己復帰方法

カスケーディングが役立つ傾向がある別のシナリオは、テスト中のコードが「流暢なインターフェース」を使用する場合です。 ここで、「ビルダー」オブジェクトは、ほとんどのメソッドから戻ります。 最終的なオブジェクトや状態を生成するメソッド呼び出しチェーンになります。 以下のテスト例では、java.lang.ProcessBuilderクラスを模擬します。

@Test
public void createOSProcessToCopyTempFiles(@Mocked ProcessBuilder pb) throws Exception {
   // Code under test creates a new process to execute an OS-specific command.
   String cmdLine = "copy /Y *.txt D:\\TEMP";
   File wrkDir = new File("C:\\TEMP");
   Process copy = new ProcessBuilder().command(cmdLine).directory(wrkDir).inheritIO().start();
   int exit = copy.waitFor();
   ...

   // Verify the desired process was created with the correct command.
   new Verifications() {{ pb.command(withSubstring("copy")).start(); }};
}

上記、方法command(...)、directory(...)およびinheritIO()一方で、作成するプロセスを設定しstart()、最終的にそれを作成します。 モックされたプロセスビルダオブジェクトpbは、これらの呼び出しから自動的に自身( " ")を返しますProcessが、呼び出しから新しいmockedも返しstart()ます。

特定のインスタンスへの呼び出しの照合

以前、我々は期待がな「と、嘲笑インスタンスに記録されていることを説明しabc .someMethod();」実際に呼び出し合っているでしょうDependencyAbc#someMethod()上の任意の嘲笑のインスタンスDependencyAbcクラスを。 ほとんどの場合、テストされたコードは、特定の依存関係の単一のインスタンスを使用します。 したがって、これは実際には問題ではなく、侮られたインスタンスがテスト対象のコードに渡されるか、内部で作成されるかにかかわらず、しかし、呼び出しが特定のインスタンス、テスト中のコードで使用されるいくつかのものの間?また、同じクラスの他のインスタンスがロックされていない状態で、擬似クラスの1つまたは少数のインスタンスだけが実際に偽装されるべき場合はどうでしょうか? (標準Javaライブラリーから、または他のサードパーティのライブラリからクラスは、嘲笑されている場合、この第二の場合は、より頻繁に発生しやすい。 )APIは、モックアノテーション提供する@Injectableだけ模擬し、1つのを残して、嘲笑タイプのインスタンスを影響を受けない。 さらに、擬似クラスのすべてのインスタンスを@Mockedモックしながら、特定のインスタンスへの期待値のマッチングを制限するいくつかの方法があります。

注入可能な模擬インスタンス

特定のクラスの複数のインスタンスで動作するコードをテストする必要があると仮定します。 そのうちのいくつかは模擬したいものです。 嘲笑するインスタンスができれば合格または注入されたテスト対象のコードの中に、我々は宣言でき@Injectableそれのためのモックフィールドまたはモックパラメータを。 この@Injectableインスタンスは "排他的な"模倣されたインスタンスになります。 別のモックフィールド/パラメータから取得されない限り、同じ模擬型の他のインスタンスは、通常の模擬型ではありません。

使用時には@Injectable、staticメソッドとコンストラクタも偽装から除外されます。 結局のところ、staticメソッドはクラスのインスタンスに関連付けられていませんが、コンストラクタは新しく作成された(したがって別の)インスタンスにのみ関連付けられます。

たとえば、次のクラスをテストするとします。

public final class ConcatenatingInputStream extends InputStream
{
   private final Queue<InputStream> sequentialInputs;
   private InputStream currentInput;

   public ConcatenatingInputStream(InputStream... sequentialInputs) {
      this.sequentialInputs = new LinkedList<InputStream>(Arrays.asList(sequentialInputs));
      currentInput = this.sequentialInputs.poll();
   }

   @Override
   public int read() throws IOException {
      if (currentInput == null) return -1;

      int nextByte = currentInput.read();

      if (nextByte >= 0) {
         return nextByte;
      }

      currentInput = sequentialInputs.poll();
      return read();
   }
}

このクラスはByteArrayInputStream、入力用のオブジェクトを使って嘲笑することなく簡単にテストできますInputStream#read()が、コンストラクタで渡された各入力ストリームでメソッドが正しく呼び出されるようにしたいとします。 次のテストでこれが達成されます。

@Test
public void concatenateInputStreams(
   @Injectable InputStream input1, @Injectable InputStream input2
) throws Exception {
   new Expectations() {{
      input1.read(); returns(1, 2, -1);
      input2.read(); returns(3, -1);
   }};

   InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2);
   byte[] buf = new byte[3];
   concatenatedInput.read(buf);

   assertArrayEquals(new byte[] {1, 2, 3}, buf);
}

@Injectableテスト中のクラスはモックされたクラスを拡張し、ConcatenatingInputStream実際に呼び出されるメソッドは実際には基本InputStreamクラスで定義されるため、ここでの使用が実際には必要であることに注意してください。 InputStream「普通に」嘲笑された場合、read(byte[])メソッドは呼び出されたインスタンスに関係なく、常に嘲笑されます。

複数の模擬インスタンスの宣言

@Mockedまたはを使用すると@Capturing(@Injectable同じモックフィールド/パラメータではない)、再生呼び出しを特定の模擬インスタンスに記録された期待値に一致させることができます。 そのためには、次の例に示すように、同じモックされた型の複数の模擬フィールドまたはパラメータを宣言するだけです。

@Test
public void matchOnMockInstance(
   @Mocked Collaborator mock, @Mocked Collaborator otherInstance
) {
   new Expectations() {{ mock.getValue(); result = 12; }};

   // Exercise code under test with mocked instance passed from the test:
   int result = mock.getValue();
   assertEquals(12, result);

   // If another instance is created inside code under test...
   Collaborator another = new Collaborator();

   // ...we won't get the recorded result, but the default one:
   assertEquals(0, another.getValue());
}

上記のテストは、テストされたコード(ここではテストメソッド自体に埋め込まれている)がgetValue()、記録の呼び出しが行われたのとまったく同じインスタンスを呼び出す場合にのみ渡されます。 これは通常、テスト対象のコードが同じタイプの2つ以上の異なるインスタンスに対して呼び出しを行い、テストで期待されるインスタンスで特定の呼び出しが発生したことを検証したい場合に便利です。

指定されたコンストラクタで作成されたインスタンス

具体的には、テスト中のコードによって後で作成される将来のインスタンスの場合、JMockitは、呼び出しに一致させることができる2つのメカニズムを提供します。 どちらのメカニズムも、模擬クラスの特定のコンストラクタ呼び出し( " new"式)に期待値を記録する必要があります。

最初のメカニズムは、インスタンスメソッドに対する期待を記録するときに、記録されたコンストラクタの期待値から取得した新しいインスタンスを単純に使用することです。 例を見てみましょう。

@Test public void newCollaboratorsWithDifferentBehaviors(@Mocked Collaborator anyCollaborator) {

  // Record different behaviors for each set of instances:
  new Expectations() {{
     // One set, instances created with "a value":
     Collaborator col1 = new Collaborator("a value");
     col1.doSomething(anyInt); result = 123;
     // Another set, instances created with "another value":
     Collaborator col2 = new Collaborator("another value");
     col2.doSomething(anyInt); result = new InvalidStateException();
  }};
  // Code under test:
  new Collaborator("a value").doSomething(5); // will return 123
  ...
  new Collaborator("another value").doSomething(0); // will throw the exception
  ...

}

上記のテストでは、を使用して、目的のクラスのモックフィールドまたはモックパラメータを宣言し@Mockedます。 しかし、この模擬フィールド/パラメータは、期待値を記録する際には使用されません。 代わりに、インスタンシエーション・レコーディングで作成されたインスタンスを使用して、インスタンス・メソッドに対するさらなる期待を記録します。 一致するコンストラクタ呼び出しで作成された将来のインスタンスは、記録されたインスタンスにマッピングされます。 また、必ずしも1対1のマッピングではなく、潜在的な多くの将来のインスタンスから記録された期待に使用される単一のインスタンスへの多対1のマッピングであることにも注意してください。

2番目のメカニズムでは、記録されたコンストラクタ呼び出しに一致する将来のインスタンスの置換インスタンスを記録できます。 この代替メカニズムでは、次のようにテストを書き直すことができます。

@Test
public void newCollaboratorsWithDifferentBehaviors(@Mocked Collaborator col1, @Mocked Collaborator col2) {
   new Expectations() {{
      // Map separate sets of future instances to separate mock parameters:
      new Collaborator("a value"); result = col1;
      new Collaborator("another value"); result = col2;

      // Record different behaviors for each set of instances:
      col1.doSomething(anyInt); result = 123;
      col2.doSomething(anyInt); result = new InvalidStateException();
   }};

   // Code under test:
   new Collaborator("a value").doSomething(5); // will return 123
   ...
   new Collaborator("another value").doSomething(0); // will throw the exception
   ...
}

両方のバージョンのテストは同等です。 2番目の方法は、部分的な模倣と組み合わされたときに、実際の(モックされていない)インスタンスを置換として使用することも可能にします。

部分的な嘲笑

デフォルトでは、擬似型とそのスーパー型(を除く)で呼び出すことができるすべてのメソッドとコンストラクタが偽装されます。 これはほとんどのテストに適していますが、状況によっては、特定のメソッドまたはコンストラクタのみを選択してモックする必要がある場合もあります。 そうでなければモックされた型で擬似されていないメソッド/コンストラクタは、呼び出されると正常に実行されます。 java.lang.Object

クラスやオブジェクトが部分的に嘲笑されたとき、JMockitは、どの期待値が記録され、どの期待値が記録されていないかに基づいて、テスト中のコードから呼び出されるときにメソッドまたはコンストラクタの実際の実装を実行するかどうかを決定します。 次のテスト例は、それを実証するでしょう。

public class PartialMockingTest
{
   static class Collaborator
   {
      final int value;

      Collaborator() { value = -1; }
      Collaborator(int value) { this.value = value; }

      int getValue() { return value; }
      final boolean simpleOperation(int a, String b, Date c) { return true; }
      static void doSomething(boolean b, String s) { throw new IllegalStateException(); }
   }

   @Test
   public void partiallyMockingAClassAndItsInstances() {
      Collaborator anyInstance = new Collaborator();

      new Expectations(Collaborator.class) {{
         anyInstance.getValue(); result = 123;
      }};

      // Not mocked, as no constructor expectations were recorded:
      Collaborator c1 = new Collaborator();
      Collaborator c2 = new Collaborator(150);

      // Mocked, as a matching method expectation was recorded:
      assertEquals(123, c1.getValue());
      assertEquals(123, c2.getValue());

      // Not mocked:
      assertTrue(c1.simpleOperation(1, "b", null));
      assertEquals(45, new Collaborator(45).value);
   }

   @Test
   public void partiallyMockingASingleInstance() {
      Collaborator collaborator = new Collaborator(2);

      new Expectations(collaborator) {{
         collaborator.getValue(); result = 123;
         collaborator.simpleOperation(1, "", null); result = false;

         // Static methods can be dynamically mocked too.
         Collaborator.doSomething(anyBoolean, "test");
      }};

      // Mocked:
      assertEquals(123, collaborator.getValue());
      assertFalse(collaborator.simpleOperation(1, "", null));
      Collaborator.doSomething(true, "test");

      // Not mocked:
      assertEquals(2, collaborator.value);
      assertEquals(45, new Collaborator(45).getValue());
      assertEquals(-1, new Collaborator().getValue());
   }
}

上に示したように、Expectations(Object ...)コンストラクタは、部分的に侮られる1つ以上のクラスまたはオブジェクトを受け入れます。 Classオブジェクトが与えられた場合、そのクラスに定義されているすべてのメソッドとコンストラクタ、さらにそのスーパークラスのメソッドとコンストラクタを嘲笑することができます。 指定されたクラスのすべてのインスタンスは、模擬インスタンスと見なされます。 一方、正規のインスタンスが与えられた場合、クラス階層内のコンストラクタではなくメソッドだけが嘲笑されます。 その特定のインスタンスだけが嘲笑されます。

これらの2つのサンプル・テストでは、模擬フィールドや模擬パラメータはありません。 部分的なmockingコンストラクタは、mocked型を指定するさらに別の方法を効果的に提供します。 また、ローカル変数に格納されたオブジェクトを模擬インスタンスにすることもできます。 このようなオブジェクトは、内部インスタンスフィールドに任意の量の状態で作成できます。 彼らは嘲笑されたときにその状態を保つでしょう。

クラスまたはインスタンスを部分的に嘲笑するよう要求した場合、検証されたメソッド/コンストラクタが記録されていなくても、呼び出しが検証される可能性があります。 たとえば、次のテストを考えてみましょう。

@Test
public void partiallyMockingAnObjectJustForVerifications() {
   Collaborator collaborator = new Collaborator(123);

   new Expectations(collaborator) {};

   // No expectations were recorded, so nothing will be mocked.
   int value = collaborator.getValue(); // value == 123
   collaborator.simpleOperation(45, "testing", new Date());
   ...

   // Unmocked methods can still be verified:
   new Verifications() {{ c1.simpleOperation(anyInt, anyString, (Date) any); }};
}

最後に、テストされたクラスに部分的な模擬を適用するより簡単な方法は、テストクラスのフィールドを両方とも@Tested(以下のセクションを参照)と注釈付きにすること@Mockedです。 この場合、テストされたオブジェクトはExpectationsコンストラクタに渡されませんが、模擬結果を必要とするすべてのメソッドに期待を記録する必要があります。

不特定の実装クラスを模擬する

この機能についての議論は、以下の(考案された)コードに基づいています。

public interface Service { int doSomething(); }
final class ServiceImpl implements Service { public int doSomething() { return 1; } }

public final class TestedUnit
{
   private final Service service1 = new ServiceImpl();
   private final Service service2 = new Service() { public int doSomething() { return 2; } };

   public int businessOperation() {
      return service1.doSomething() + service2.doSomething();
   }
}

私たちがテストしたいメソッドはbusinessOperation()、別のインターフェースを実装するクラスを使用しますService。 これらの実装の1つは、クライアントコードから完全にアクセスできない(Reflectionの使用を除く)匿名の内部クラスによって定義されます。

基本型(それが与えられinterface、abstractクラス、または基底クラスの任意の並べ替え)、我々は唯一の基本型を知っているが、すべての拡張/実装する実装クラスは嘲笑を受けるテストを書くことができます。 そうするために、既知の基底型のみを参照する "取り込み"模擬型を宣言します。 JVMによってすでに読み込まれた実装クラスだけが嘲笑されるだけでなく、後でテストを実行する際にJVMによってロードされる追加のクラスも擬似的に取得されます。 この能力は@Capturingアノテーションによって有効になります。 この注釈は、以下に示すように、模擬フィールドや模擬パラメータに適用できます。

public final class UnitTest
{
   @Capturing Service anyService;

   @Test
   public void mockingImplementationClassesFromAGivenBaseType() {
      new Expectations() {{ anyService.doSomething(); returns(3, 4); }};

      int result = new TestedUnit().businessOperation();

      assertEquals(7, result);
   }
}

上記のテストでは、Service#doSomething()メソッドに対して2つの戻り値が指定されています。 この期待は関係なく呼び出しが発生した実際のインスタンスの、このメソッドへのすべての呼び出しを一致、あろうと関係なく方法を実施する実際のクラスの。

完全な検証とその他のバリエーション

検証APIは非常に豊富で、次のセクションで説明するように、あらゆる種類のさまざまな検証シナリオをサポートしています。 しかし、これらは頻繁には使用されません。 ほとんどのテストでは、前に説明したように、基本的な順序付けされた検証のみが必要です。

完全な検証

場合によっては、テストに関連する模擬型/インスタンスへのすべての呼び出しを検証することが重要な場合があります。 そのような場合、ブロックは未確認のまま残されていないことを確認します。 new FullVerifications () {...}

@Test
public void verifyAllInvocations(@Mocked Dependency mock) {
   // Code under test included here for easy reference:
   mock.setSomething(123);
   mock.setSomethingElse("anotherValue");
   mock.setSomething(45);
   mock.save();

   new FullVerifications() {{
      // Verifications here are unordered, so the following invocations could be in any order.
      mock.setSomething(anyInt); // verifies two actual invocations
      mock.setSomethingElse(anyString);
      mock.save(); // if this verification (or any other above) is removed the test will fail
   }};
}

記録された期待値に対して下限(最小呼び出し回数の制約)が指定されている場合、この制約はテストの最後に常に暗黙的に検証されることに注意してください。 したがって、完全な検証ブロック内でそのような期待を明示的に検証する必要はありません。

呼び出しが起こらなかったことを確認する

これを検証ブロック内で行うtimes = 0には、再生フェーズ中に起こっていないと思われる呼び出しの直後に割り当てを追加してください。 " 1つ以上の一致する呼び出しが行われた場合、テストは失敗します。

モックされたタイプのセットを完全に検証するように制限する

デフォルトでは、すべての呼び出しのすべて「を使用する際に特定のテストのための有効な嘲笑のインスタンス/タイプを明示的に検証されなければならないnew FullVerifications() {}」ブロックを。 さて、2つ(またはそれ以上)の模擬型のテストがあっても、そのうちの1つ(または2つより多い場合は模擬型のサブセット)に対する呼び出しを完全に検証したいだけです。 答えはFullVerifications(mockedTypesAndInstancesToVerify)コンストラクタを使用することです。 与えられた模擬インスタンスと模擬型(クラスオブジェクト/リテラル​​)のみが考慮されます。 次のテストでは例を示します。

@Test
public void verifyAllInvocationsToOnlyOneOfTwoMockedTypes(
   @Mocked Dependency mock1, @Mocked AnotherDependency mock2
) {
   // Inside code under test:
   mock1.prepare();
   mock1.setSomething(123);
   mock2.doSomething();
   mock1.editABunchMoreStuff();
   mock1.save();

   new FullVerifications(mock1) {{
      mock1.prepare();
      mock1.setSomething(anyInt);
      mock1.editABunchMoreStuff();
      mock1.save(); times = 1;
   }};
}

上記のテストでは、mock2.doSomething()呼び出しは決して確認されません。

単一の模擬クラスのメソッド/コンストラクタにのみ検証を制限するには、クラスリテラルをFullVerifications(...)コンストラクタに渡します。 たとえば、new FullVerifications(AnotherDependency.class) { ... }ブロックは、模擬AnotherDependencyクラスへのすべての呼び出しが検証されたことを確認するだけです。

呼び出しが発生していないことを確認する

テストで使用された疑似型/インスタンスで呼び出しがまったく発生していないことを確認するには、空の完全検証ブロックを追加します。 常にそうであるように、指定された制約によって期待どおりに記録された期待値は暗黙的に検証され、したがって完全な検証ブロックによって無視されることに注意してください。 そのような場合、空の検証ブロックは、他の呼び出しが発生していないことを確認する。 さらに、同じテストで以前の検証ブロックで何らかの期待値が検証された場合、それらは完全検証ブロックによっても無視されます。 times / minTimes

テストで2つ以上の擬似型が使用されていて、そのうちのいくつかに対して呼び出しが発生していないことを確認する場合は、コンストラクタ内の目的の擬似型および/またはインスタンスを空の検証ブロックに指定します。 テストの例を次に示します。

@Test
public void verifyNoInvocationsOnOneOfTwoMockedDependenciesBeyondThoseRecordedAsExpected(
   @Mocked Dependency mock1, @Mocked AnotherDependency mock2
) {
   new Expectations() {{
      // These two are recorded as expected:
      mock1.setSomething(anyInt);
      mock2.doSomething(); times = 1;
   }};

   // Inside code under test:
   mock1.prepare();
   mock1.setSomething(1);
   mock1.setSomething(2);
   mock1.save();
   mock2.doSomething();

   // Will verify that no invocations other than to "doSomething()" occurred on mock2:
   new FullVerifications(mock2) {};
}

発生してはならない不特定の呼び出しを確認する

完全な検証ブロックを使用すると、特定のメソッドやコンストラクタが呼び出されることはありません。 それぞれのメソッドやコンストラクタは対応するtimes = 0代入で記録または検証する必要はありません。 次のテストでは例を示します。

@Test
public void readOnlyOperation(@Mocked Dependency mock) {
   new Expectations() {{ mock.getData(); result = "test data"; }};

   // Code under test:
   String data = mock.getData();
   // mock.save() should not be called here
   ...

   new FullVerifications() {{
      mock.getData(); minTimes = 0; // calls to getData() are allowed, others are not
   }};
}

Dependency検証ブロック(Dependency#getData()この場合)で明示的に検証されたものを除き、再生フェーズ中にクラスの任意のメソッド(またはコンストラクタ)への呼び出しが発生した場合、上記のテストは失敗します。


トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS