Mockito and Hamcrest: how to verify invocation of Collection argument?

58,456

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.

Share:
58,456
Philipp Jardas
Author by

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, 2022

Comments

  • Philipp Jardas
    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(...) 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!

  • Jeff Bowman
    Jeff Bowman over 10 years
    @David You should. :) You're welcome to, after the fact, for the proper credit.
  • Philipp Jardas
    Philipp Jardas over 10 years
    thanks 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
    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
    Jeff Bowman over 10 years
    contains returns a Matcher<Iterable<? extends String>>, not a Matcher<Iterable<String>>, so without @SuppressWarnings you're going to get an unsafe cast warning in there somewhere. Same goes for containsInAnyOrder, which would also work.
  • barracuda317
    barracuda317 almost 6 years
    which contains do you use? i only finde contains(String substring)
  • d4vidi
    d4vidi about 5 years
    Just 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
    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
    findusl over 4 years
    Since 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
    Jacob van Lingen about 3 years
    Awesome! And if you want to check for an empty collection you could just use argThat(Collection::isEmpty).
  • user07
    user07 over 2 years
    This example depends on the order of elements in the list.