Reputation: 3302
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(...)
is Matcher<Iterable<? extends E>>
, so Matchers.argThat(...)
returns Iterable<String>
in my case, which naturally does not apply to the required Collection<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!
Upvotes: 61
Views: 76404
Reputation: 4210
Similar to another answer here you can do the following:
verify(yourmock, times(1)).yourmethod(argThat(arg -> arg.containsAll(asList("a", "b"))));
Upvotes: 1
Reputation: 46470
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");
Upvotes: 13
Reputation: 2773
You can put your own lambda as an ArgumentMatcher
when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
.thenReturn(...);
Upvotes: 14
Reputation: 79847
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.
Upvotes: 41
Reputation: 95704
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.
Upvotes: 12
Reputation: 20375
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.
Upvotes: 1
Reputation: 8865
You could have your own java.util.Collection implementation and override the equals method like below.
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;
}
}
Upvotes: -1