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.
Philipp Jardas
Passionate agile full-stack software generalist, TEN SINGer, musician, and Ultimate Frisbee player. I design and build scalable and resilient software components with TypeScript in a serverless environment. I like to learn new technology and to balance on the cutting edge. With more than 20 years of experience in software development I have probably made every mistake possible, and I've learned a lot from it.
Updated on July 09, 2022Comments
-
Philipp Jardas almost 2 years
I'm running into a generics problem with Mockito and Hamcrest.
Please assume the following interface:
public interface Service { void perform(Collection<String> elements); }
And the following test snippet:
Service service = mock(Service.class); // ... perform business logic verify(service).perform(Matchers.argThat(contains("a", "b")));
So I want to verify that my business logic actually called the service with a collection that contains "a" and "b" in that order.
However, the return type of
contains(...)
isMatcher<Iterable<? extends E>>
, soMatchers.argThat(...)
returnsIterable<String>
in my case, which naturally does not apply to the requiredCollection<String>
.I know that I could use an argument captor as proposed in Hamcrest hasItem and Mockito verify inconsistency, but I would very much like not to.
Any suggestions! Thanks!
-
Jeff Bowman over 10 years@David You should. :) You're welcome to, after the fact, for the proper credit.
-
Philipp Jardas over 10 yearsthanks for your answer and the hint that Mockito matchers return
null
. If @DavidWallace does add his comment as a reply, I will accept his answer though. Hope you won't mind. :-) -
Jeff Bowman over 10 years@Philupp It's only fair! He just did answer, FWIW, so I'm editing down mine to avoid the repetition.
-
Jeff Bowman over 10 years
contains
returns aMatcher<Iterable<? extends String>>
, not aMatcher<Iterable<String>>
, so without@SuppressWarnings
you're going to get an unsafe cast warning in there somewhere. Same goes forcontainsInAnyOrder
, which would also work. -
barracuda317 almost 6 yearswhich contains do you use? i only finde contains(String substring)
-
d4vidi about 5 yearsJust so as to keep this up to date; an easy way to do this with Kotlin is:
verify(service).perform(argThat { "b" == this["a"] })
-
Stephen Ruda over 4 years@barracuda317 they are using org.hamcrest.Matchers.contains(). I managed to solve this using org.hamcrest.CoreMatchers.hasItems() instead.
-
findusl over 4 yearsSince mockito 2.1.0 you need to use MockitoHamcrest.argThat. javadoc.io/doc/org.mockito/mockito-core/2.2.9/org/mockito/…
-
Jacob van Lingen about 3 yearsAwesome! And if you want to check for an empty collection you could just use
argThat(Collection::isEmpty)
. -
user07 over 2 yearsThis example depends on the order of elements in the list.