1. イントロ

この記事は、JMockitシリーズの第2弾です。 JMockitの基本に既に精通していることを前提としているため、最初の記事を読むことをお勧めします。

今日はさらに深く掘り下げて、期待に焦点を合わせます。 より具体的または一般的な引数マッチングを定義する方法と、値を定義するより高度な方法を示します。

2. 一致する引数値

次のアプローチは、ExpectationsVerificationsの両方に適用されます。

2.1. 「任意の」フィールド

JMockitは、引数のマッチングをより一般的にするための一連のユーティリティフィールドを提供します。 これらのユーティリティの1つは、anyXフィールドです。

これらは、任意の値が渡されたことを確認し、プリミティブ型(および対応するラッパークラス)ごとに1つ、文字列用に1つ、Object型の「ユニバーサル」な値があります。

例を見てみましょう:

public interface ExpectationsCollaborator {
    String methodForAny1(String s, int i, Boolean b);
    void methodForAny2(Long l, List<String> lst);
}

@Test
public void test(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForAny1(anyString, anyInt, anyBoolean); 
        result = "any";
    }};

    Assert.assertEquals("any", mock.methodForAny1("barfooxyz", 0, Boolean.FALSE));
    mock.methodForAny2(2L, new ArrayList<>());

    new FullVerifications() {{
        mock.methodForAny2(anyLong, (List<String>) any);
    }};
}

any フィールドを使用するときは、期待されるタイプにキャストする必要があることを考慮に入れる必要があります。 フィールドの完全なリストは、ドキュメントにあります。

2.2. 「with」メソッド

JMockitは、一般的な引数の照合に役立ついくつかのメソッドも提供します。 これらはwithXメソッドです。

これらにより、anyXフィールドよりも少し高度なマッチングが可能になります。 ここでは、 foo 、1に等しくない整数、null以外のブール値を含む文字列でトリガーされるメソッドの期待値を定義する例を見ることができます。 ]およびListクラスの任意のインスタンス:

public interface ExpectationsCollaborator {
    String methodForWith1(String s, int i);
    void methodForWith2(Boolean b, List<String> l);
}

@Test
public void testForWith(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForWith1(withSubstring("foo"), withNotEqual(1));
        result = "with";
    }};

    assertEquals("with", mock.methodForWith1("barfooxyz", 2));
    mock.methodForWith2(Boolean.TRUE, new ArrayList<>());

    new Verifications() {{
        mock.methodForWith2(withNotNull(), withInstanceOf(List.class));
    }};
}

withX メソッドの完全なリストは、JMockitのドキュメントで確認できます。

特別なwith(Delegate)および withArgThat(Matcher)は、それぞれのサブセクションでカバーされることを考慮に入れてください。

2.3. ヌルはヌルではありません

遅かれ早かれ理解するのに良いことは、nullnullがモックに渡された引数を定義するために使用されていないということです。

実際には、 null シンタックスシュガーとして使用され、オブジェクトが渡されることを定義します(したがって、参照型のパラメーターにのみ使用できます)。 特定のパラメーターがnull参照を受け取ることを具体的に確認するには、 withNull()マッチャーを使用できます。

次の例では、渡された引数が次の場合にトリガーされるモックの動作を定義します:任意の文字列、任意のリスト、および null参照:

public interface ExpectationsCollaborator {
    String methodForNulls1(String s, List<String> l);
    void methodForNulls2(String s, List<String> l);
}

@Test
public void testWithNulls(@Mocked ExpectationsCollaborator mock){
    new Expectations() {{
        mock.methodForNulls1(anyString, null); 
        result = "null";
    }};
    
    assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList<String>()));
    mock.methodForNulls2("blablabla", null);
    
    new Verifications() {{
        mock.methodForNulls2(anyString, (List<String>) withNull());
    }};
}

違いに注意してください。nullは任意のリストを意味し、 withNull()はリストへのnull参照を意味します。 特に、これにより、宣言されたパラメーター型に値をキャストする必要がなくなります(3番目の引数をキャストする必要がありましたが、2番目の引数はキャストする必要がありませんでした)。

これを使用できる唯一の条件は、少なくとも1つの明示的な引数マッチャーが期待値に使用されていることです(withメソッドまたはanyフィールドのいずれか)。

2.4. 「タイムズ」フィールド

場合によっては、モックされたメソッドに期待される呼び出しの数制限したいことがあります。 このため、JMockitには予約語 times minTimes 、および maxTimes があります(3つすべてが非負の整数のみを許可します)。

public interface ExpectationsCollaborator {
    void methodForTimes1();
    void methodForTimes2();
    void methodForTimes3();
}

@Test
public void testWithTimes(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForTimes1(); times = 2;
        mock.methodForTimes2();
    }};
    
    mock.methodForTimes1();
    mock.methodForTimes1();
    mock.methodForTimes2();
    mock.methodForTimes3();
    mock.methodForTimes3();
    mock.methodForTimes3();
    
    new Verifications() {{
        mock.methodForTimes3(); minTimes = 1; maxTimes = 3;
    }};
}

この例では、 methodForTimes1()の正確に2回の呼び出し(1回ではなく、3回ではなく、正確に2回)は、 times =2;の行を使用して実行する必要があると定義しました。

次に、デフォルトの動作(繰り返し制約が指定されていない場合 minTimes = 1; が使用される場合)を使用して、 methodForTimes2()。に対して少なくとも1回の呼び出しが行われることを定義しました。

最後に、 minTimes =1;に続いてmaxTimes= 3; を使用して、 methodForTimes3()に対して1〜3回の呼び出しが発生することを定義しました。

minTimes が最初に割り当てられている限り、minTimesmaxTimesの両方を同じ期待値に指定できることを考慮してください。 一方、timesは単独でのみ使用できます。

2.5. カスタム引数マッチング

引数の照合は、値を指定したり、事前定義されたユーティリティ(anyXまたはwithX)を使用したりするほど直接的ではない場合があります。

その場合、JMockitはHamcrestMatcherインターフェースに依存します。 特定のテストシナリオのマッチャーを定義し、そのマッチャーを withArgThat()呼び出しで使用する必要があります。

特定のクラスを渡されたオブジェクトに一致させる例を見てみましょう。

public interface ExpectationsCollaborator {
    void methodForArgThat(Object o);
}

public class Model {
    public String getInfo(){
        return "info";
    }
}

@Test
public void testCustomArgumentMatching(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForArgThat(withArgThat(new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object item) {
                return item instanceof Model && "info".equals(((Model) item).getInfo());
            }

            @Override
            public void describeTo(Description description) { }
        }));
    }};
    mock.methodForArgThat(new Model());
}

3. 戻り値

次に、戻り値を見てみましょう。 検証には戻り値を定義できないため、以下のアプローチは期待値にのみ適用されることに注意してください。

3.1. 結果と返品(…)

JMockitを使用する場合、モックされたメソッドの呼び出しの期待される結果を定義する3つの異なる方法があります。 3つすべてのうち、最初の2つ(最も単純なもの)について説明します。これは、90% ofの日常的なユースケースを確実にカバーします。

これらの2つは、 resultフィールドとreturns(Object…)メソッドです。

  • result フィールドを使用すると、void以外のモックメソッドを返すone戻り値を定義できます。 この戻り値は、スローされる例外になることもあります(今回は、非voidとvoidの両方の戻りメソッドで機能します)。
    • 複数のメソッド呼び出しに対して複数の値を返すために、いくつかの result フィールドの割り当てを行うことができます(スローされる戻り値とエラーの両方を混在させることができます)。
    • result に値のリストまたは配列(モックされたメソッドの戻り型と同じ型、ここでは例外なし)を割り当てる場合も、同じ動作が実現されます。
  • returns(Object…)メソッドは、同時に複数の値を返すためのシンタックスシュガーです。

これは、コードスニペットでより簡単に示されます。

public interface ExpectationsCollaborator{
    String methodReturnsString();
    int methodReturnsInt();
}

@Test
public void testResultAndReturns(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodReturnsString();
        result = "foo";
        result = new Exception();
        result = "bar";
        returns("foo", "bar");
        mock.methodReturnsInt();
        result = new int[]{1, 2, 3};
        result = 1;
    }};

    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    try {
        mock.methodReturnsString();
        fail("Shouldn't reach here");
    } catch (Exception e) {
        // NOOP
    }
    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
    assertEquals("Should return 2", 2, mock.methodReturnsInt());
    assertEquals("Should return 3", 3, mock.methodReturnsInt());
    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
}

この例では、 methodReturnsString()への最初の3回の呼び出しで、期待収益は(順番に)「foo」、例外、および「bar」であると定義しました。 。 これは、resultフィールドへの3つの異なる割り当てを使用して実現しました。

次に、行14 で、4番目と5番目の呼び出しについて、 “foo”“bar”returns(オブジェクト…)メソッド。

methodReturnsInt()の場合、行13 で、 result フィールドに異なる結果の配列を割り当てることにより、1、2、最後に3を返すように定義しました。 行15は、resultフィールドへの単純な割り当てによって1を返すように定義しました。

ご覧のとおり、モックされたメソッドの戻り値を定義する方法はいくつかあります。

3.2. 委任者

この記事を終了するために、戻り値を定義する3番目の方法であるDelegateインターフェースについて説明します。 このインターフェースは、モックされたメソッドを定義するときに、より複雑な戻り値を定義するために使用されます。

簡単に説明する例を見ていきます。

public interface ExpectationsCollaborator {
    int methodForDelegate(int i);
}

@Test
public void testDelegate(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForDelegate(anyInt);
            
        result = new Delegate() {
            int delegate(int i) throws Exception {
                if (i < 3) {
                    return 5;
                } else {
                    throw new Exception();
                }
            }
        };
    }};

    assertEquals("Should return 5", 5, mock.methodForDelegate(1));
    try {
        mock.methodForDelegate(3);
        fail("Shouldn't reach here");
    } catch (Exception e) {
    }
}

委任子を使用する方法は、委任子の新しいインスタンスを作成し、それをreturnsフィールドに割り当てることです。 この新しいインスタンスでは、モックされたメソッドと同じパラメーターと戻りタイプを使用して新しいメソッドを作成する必要があります(任意の名前を使用できます)。 この新しいメソッド内で、必要な値を返すために必要な実装を使用します。

この例では、モックされたメソッドに渡された値が 3 未満の場合に、 5 が返され、それ以外の場合は例外がスローされる実装を行いました( times = 2; を使用して、戻り値を定義することでデフォルトの動作を失ったため、2番目の呼び出しが予期されるようにします。

かなり多くのコードのように見えるかもしれませんが、場合によっては、それが私たちが望む結果を達成する唯一の方法になります。

4. 結論

これにより、日常のテストの期待と検証を作成するために必要なすべてのものを実際に示しました。

もちろん、JMockitについてはさらに多くの記事を公開する予定ですので、さらに多くのことを学ぶのを楽しみにしていてください。

そして、いつものように、このチュートリアルの完全な実装はGitHubプロジェクトにあります。

4.1. シリーズの記事

シリーズのすべての記事: