Conflicting overloads for Hamcrest matcher

4.2k Views Asked by At

The matcher IsIterableContainingInAnyOrder has two overloads for the static factory method containsInAnyOrder (both have the return type Matcher<java.lang.Iterable<? extends T>>):

  1. containsInAnyOrder(java.util.Collection<Matcher<? super T>> itemMatchers)
  2. containsInAnyOrder(Matcher<? super T>... itemMatchers)

Now consider the following program:

import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

import java.util.Arrays;

import org.junit.Test;

public class SomeTest {

    @SuppressWarnings("unchecked")
    @Test
    public void foo() {
        assertThat(Arrays.asList("foo","bar"), 
                       containsInAnyOrder(equalTo("foo"), equalTo("bar")));
    }

}

When executing this as a JUnit test, it passes, as expected. It uses the second overload of containsInAnyOrder shown above.

Now, when I change the assertion to this (which exactly matches the example given in the documentation of the first overload):

assertThat(Arrays.asList("foo","bar"), 
           containsInAnyOrder(Arrays.asList(equalTo("foo"), equalTo("bar"))));
                              ^^^^^^^^^^^^^^

it doesn't compile anymore, because now the compiler infers the return type of containsInAnyOrder to be

Matcher<Iterable<? extends List<Matcher<String>>>>

It seems like the compiler still chooses the second overload. If it used the first one, the example should work. Why does it behave like this? How can I make this work?

I am using Hamcrest 1.3 and Oracle Java 1.7.

3

There are 3 best solutions below

1
On

It actually matches both overloaded methods. I'm not sure why exactly the first one is chosen, but you can provide a hint to make it choose the correct method.

By casting the argument to Collection:

assertThat(Arrays.asList("foo","bar"),
        containsInAnyOrder((Collection)Arrays.asList(equalTo("foo"), equalTo("bar"))));

or by specifying the generic type T as <String> (don't work with static import, though):

assertThat(Arrays.asList("foo","bar"),
        IsIterableContainingInAnyOrder.<String>containsInAnyOrder(Arrays.asList(equalTo("foo"), equalTo("bar"))));
1
On

This is even a bit harder when you are matching your own objects, rather than simple strings, to get the generics to work out. If you are using the varargs containsInAnyOrder(Matcher<? super T>... itemMatchers) as in the question's first example you will get a Unchecked generics array creation for varargs parameter warning. For example:

assertThat(myDTOList, 
    containsInAnyOrder(sameStateAs(expectedMyDTO1), sameStateAs(expectedMyDTO2));

One way to then solve the problem the OP stated in the question, is to define your collection of matchers as follows:

Collection<Matcher<? super MyDTO>> expectedMyDTOs = 
    Arrays.<Matcher<? super MyDTO>>asList(sameStateAs(expectedMyDTO1), sameStateAs(expectedMyDTO2));

// Use like this:
assertThat(myDTOList, 
    containsInAnyOrder(expectedMyDTOs);
0
On

With hamcrest 1.3 you are able to use the Matchers class instead of IsIterableContainingInAnyOrder directly as mentioned by @eee. Matchers actually just calls IsIterableContainingInAnyOrder for you.

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

import org.hamcrest.Matchers;
import java.util.Arrays;
import org.junit.Test;

public class SomeTest
{
    @Test
    public void foo() {
        assertThat(Arrays.asList("foo","bar"), 
            Matchers.<OrderValidationStep>containsInAnyOrder("foo", "bar"));
    }
}

Note that you cannot use a static import if you want to Type your call to containsInAnyOrder, and this removes the need to add @SuppressWarnings("unchecked")