Search code examples
javagenericsmockitojmockithamcrest

Mockito/JMockit & Hamcrest matchers : How to verify Lists/Collections?


This 2013 post on SO asked how to use Hamcrest matchers to verify lists/collections invocations in Mockito. The accepted solution was to cast the Matcher to a (Collection).

I'm trying to do something similar, but running into a class cast error. I am not sure if I am misusing Hamcrest matchers, or if this usage simply isn't supported by Mockito. In my case, I'm trying to use a list of Matchers as my argument:

static class Collaborator
{
   void doSomething(Iterable<String> values) {}
}

@Test
public void usingMockito()
{
   Collaborator mock = Mockito.mock(Collaborator.class);
   mock.doSomething(Arrays.asList("a", "b"));

   // legal cast
   Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains("a", "b")));
   // legal cast
   Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Matchers.equalTo("a"), Matchers.equalTo("b"))));

   // illegal cast!!! Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>
   Mockito.verify(mock).doSomething((Collection<String>)argThat(Matchers.contains(Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b")))));
}

But I get the cast error:

Cannot cast from Iterable<capture#3-of ? extends List<Matcher<String>>> to Collection<String>

Am I doing something unsupported?


Solution

  • As Jeff Bowman has already pointed out, the problem is that the compiler doesn't know which of the 4 contains methods you are trying to call.

    The list you are constructing

    Arrays.asList(Matchers.equalTo("a"), Matchers.equalTo("b"))
    

    is of type

    List<Matcher<String>>
    

    but the contains method you want to call (<E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers)) expects a type

    List<Matcher<? super String>>
    

    as parameter. As your list type doesn't match the expected one, the compiler actually thinks that you are trying to call

    <E> Matcher<Iterable<? extends E>> contains(E... items)
    

    The solution: give the compiler what it wants. Create a List<Matcher<? super String>> instead of a List<Matcher<String>>:

            List<Matcher<? super String>> matchersList = new ArrayList<>();
            matchersList.add(Matchers.equalTo("a"));
            matchersList.add(Matchers.equalTo("b"));
    
            // no illegal cast anymore
            Mockito.verify(mock).doSomething(
                (Collection<String>) argThat(Matchers.contains(matchersList)));
    

    EDIT:

    Adding Jeff Bowman's inline solution from his comment, that enables the use of Arrays.asList as stated in the question:

    Mockito.verify(mock).doSomething(
       (Collection<String>) argThat(
            Matchers.contains(
                Arrays.<Matcher<? super String>> asList(
                    Matchers.equalTo("a"), Matchers.equalTo("b")
                )
            )
        )
    );