web-dev-qa-db-ja.com

MockitoとHamcrest:コレクション引数の呼び出しを検証する方法?

MockitoとHamcrestでジェネリックの問題に直面しています。

以下のインターフェースを想定してください:

public interface Service {
    void perform(Collection<String> elements);
}

そして、次のテストスニペット:

Service service = mock(Service.class);

// ... perform business logic

verify(service).perform(Matchers.argThat(contains("a", "b")));

そのため、ビジネスロジックが実際に「a」と「b」をこの順序で含むコレクションでサービスを呼び出したことを確認したいと思います。

ただし、contains(...)の戻り値の型はMatcher<Iterable<? extends E>>であるため、Matchers.argThat(...)Iterable<String>を返します。これは当然、必要なCollection<String>には適用されません。

Hamcrest hasItemとMockitoが矛盾を検証する で提案されているように、引数キャプターを使用できることを知っていますが、そうしないことを望みます。

助言がありますか!ありがとう!

35
Philipp Jardas

あなたはただ書くことができます

verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));

コンパイラの観点から見ると、これはIterable<String>からCollection<String>これは問題ありません。後者は前者のサブタイプだからです。実行時に、argThatnullを返すため、performなしでClassCastExceptionに渡すことができます。それに関する重要な点は、マッチャーが検証のためにMockitoの内部引数構造に到達することです。これはargThatが行うことです。

29

別の方法として、ArgumentCaptorへのアプローチを変更できます。

@SuppressWarnings("unchecked") // needed because of `List<String>.class` is not a thing
// suppression can be worked around by using @Captor on a field
ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(List.class);

verify(service).perform(captor.capture());
assertThat(captor.getValue(), contains("a", "b"));

副作用として、これにより検証がHamcrestライブラリーから分離され、他のライブラリー(例:Truth)を使用できるようになります。

assertThat(captor.getValue()).containsExactly("a", "b");
7
TWiStErRob

このような状況で動けなくなる場合は、非常に小さな再利用可能なアダプターを作成できることを忘れないでください。

verify(service).perform(argThat(isACollectionThat(contains("foo", "bar"))));

private static <T> Matcher<Collection<T>> isACollectionThat(
    final Matcher<Iterable<? extends T>> matcher) {
  return new BaseMatcher<Collection<T>>() {
    @Override public boolean matches(Object item) {
      return matcher.matches(item);
    }

    @Override public void describeTo(Description description) {
      matcher.describeTo(description);
    }
  };
}

キャストを使用した上記のDavidのソリューションが最短の正解であることに注意してください。

7
Jeff Bowman

リストに2つの項目のみが含まれていると仮定して、期待される引数で検証するだけではありません。

final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);

私は原則としてオイゲンに同意しますが、文字列の比較に等号に依存することは受け入れられると思います...さらに、containsマッチャーは比較に等号を使用します。

1
Jonathan

独自のラムダをArgumentMatcherとして置くことができます

when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
    .thenReturn(...);
0
dehasi

独自のJava.util.Collection実装を使用して、以下のようにequalsメソッドをオーバーライドできます。

public interface Service {
    void perform(Collection<String> elements);
}

@Test
public void testName() throws Exception {
    Service service = mock(Service.class);
    service.perform(new HashSet<String>(Arrays.asList("a","b")));
    Mockito.verify(service).perform(Matchers.eq(new CollectionVerifier<String>(Arrays.asList("a","b"))));
}

public class CollectionVerifier<E> extends ArrayList<E> {

    public CollectionVerifier() {

    }

    public CollectionVerifier(final Collection<? extends E> c) {
        super(c);
    }

    @Override
    public boolean equals(final Object o) {
        if (o instanceof Collection<?>) {
            Collection<?> other = (Collection<?>) o;
                return this.size() == other.size() && this.containsAll(other);
        }
        return false;
    }

}
0
Dev Blanked