Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conflicting overloads for Hamcrest matcher

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.

like image 459
Björn Pollex Avatar asked Sep 04 '13 13:09

Björn Pollex


3 Answers

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"))));
like image 135
Steinar Avatar answered Oct 17 '22 01:10

Steinar


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);
like image 22
Niel de Wet Avatar answered Oct 17 '22 01:10

Niel de Wet


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")

like image 2
Brad Avatar answered Oct 17 '22 03:10

Brad