JMockitの期待へのガイド

JMockitの期待へのガイド

1. イントロ

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

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

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

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

2.2. 「with」メソッド

JMockitは、一般的な引数のマッチングを支援するいくつかのメソッドも提供します。 これらはwithXメソッドです。

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

public interface ExpectationsCollaborator {
    String methodForWith1(String s, int i);
    void methodForWith2(Boolean b, List 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));
    }};
}

JMockitのdocumentationwithXメソッドの完全なリストを確認できます。

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

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

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

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

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

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

@Test
public void testWithNulls(@Mocked ExpectationsCollaborator mock){
    new Expectations() {{
        mock.methodForNulls1(anyString, null);
        result = "null";
    }};

    assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList()));
    mock.methodForNulls2("blablabla", null);

    new Verifications() {{
        mock.methodForNulls2(anyString, (List) withNull());
    }};
}

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

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

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

場合によっては、モックされたメソッドに期待されるconstrainthe number of invocationsが必要になります。 このため、JMockitには予約語timesminTimes、および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;
    }};
}

この例では、times = 2;行を使用してmethodForTimes1()の正確に2回の呼び出し(1回ではなく、3回ではなく、正確に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() {
            @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. 戻り値

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

3.1. 結果と戻り値(…)

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

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

  • resultフィールドを使用すると、void以外の戻りモックメソッドのone戻り値を定義できます。 この戻り値は、例外としてスローされることもあります(今回は非voidおよびvoidの両方の戻りメソッドで機能します)。

    • 複数のメソッド呼び出しに対してmore than one valueを返すために、いくつかのresultフィールドの割り当てを行うことができます(返される値とスローされるエラーの両方を混在させることができます)。

    • resultに値のリストまたは配列を割り当てると、同じ動作が実現されます(モックされたメソッドの戻り値の型と同じ型で、ここでは例外はありません)。

  • returns(Object…)メソッドは、同時に複数の値を返すためのsyntactic sugarです。

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

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

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

    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    try {
        mock.methodReturnsString();
    } catch (Exception e) { }

    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つの異なる割り当てを使用して実現しました。

次に、line 14で、4番目と5番目の呼び出しについて、returns(Object…)メソッドを使用して“foo”“bar”を返す必要があることを定義しました。

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

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

3.2. 委任者

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

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

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

@Test
public void testDelegate(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{

        mock.methodForDelegate(anyInt);
        result = new Delegate() {
            public 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);
    } catch (Exception e) { }
}

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

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

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

4. 結論

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

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

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

4.1. シリーズの記事

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