Mockito and Hamcrest: how to verify invocation of Collection argument?
Solution 1:
You can just write
verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));
From the compiler's point of view, this is casting an Iterable<String>
to a Collection<String>
which is fine, because the latter is a subtype of the former. At run time, argThat
will return null
, so that can be passed to perform
without a ClassCastException
. The important point about it is that the matcher gets onto Mockito's internal structure of arguments for verification, which is what argThat
does.
Solution 2:
As an alternative one could change the approach to 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"));
Notice, that as a side effect this decouples the verification from the Hamcrest library, and allows you to use any other library (e.g. Truth):
assertThat(captor.getValue()).containsExactly("a", "b");
Solution 3:
If you get stuck in situations like these, remember that you can write a very small reusable adapter.
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);
}
};
}
Note that David's solution above, with casting, is the shortest right answer.
Solution 4:
You can put your own lambda as an ArgumentMatcher
when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
.thenReturn(...);
Solution 5:
Why not just verify with the expected arguments, assuming the list only contains the two items, e.g.:
final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);
Whilst I agree with Eugen in principle, I think that relying on equals for String comparison is acceptable... besides, the contains
matcher uses equals for comparison anyway.