Search code examples
javaunit-testingjunithamcrest

JUnit & hamcrest: could containsInAnyOrder() tell more about the mismatch?


While testing a Set with JUnit and Hamcrest Matchers I've noticed that Matchers.contains() method gives a pretty good clue on what's wrong with the test. On the other hand Matchers.containsInAnyOrder() difference report is almost useless. Here's the test code:

Simple bean:

public class MyBean {
    private Integer id;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }       
}

JUnit test:

import java.util.HashSet;
import java.util.Set;

import org.junit.Test;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class MyTest {

    @Test
    public void hamcrestTest() {
        Set<MyBean> beanSet = new HashSet<MyBean>();

        MyBean bean = new MyBean();
        bean.setId(1);
        beanSet.add(bean);

        bean = new MyBean();
        bean.setId(2);
        beanSet.add(bean);

        assertThat(beanSet, contains(
                hasProperty("id", is(1)),
                hasProperty("id", is(3))
                ));
    }
}

As you could see actual bean id's are 1 and 2 while expected ones are 1 and 3 so the test fails.

Test result:

java.lang.AssertionError: 
Expected: iterable over [hasProperty("id", is <1>), hasProperty("id", is <3>)] in any order
     but: Not matched: <MyBean@4888884e

If I switch to Matchers.contains() method then the result is much more informative:

java.lang.AssertionError: 
Expected: iterable containing [hasProperty("id", is <1>), hasProperty("id", is <3>)]
     but: item 0: property 'id' was <2>

Unfortunately, since a Set is not ordered contains() is not an option in this case.


Finally the question:
Is it somehow possible to get a better error report when asserting a Set with hamcrest?


Solution

  • Hamcrest seems to have different implementations for how mismatches are reported for the contains and the containsInAnyOrder matchers.

    The containsInAnyOrder just gives you the item's toString() value by doing this:

    mismatchDescription.appendText("Not matched: ").appendValue(item);
    

    While the contains matcher does a better job by delegating to the actual matcher's describeMismatch():

    matcher.describeMismatch(item, mismatchDescription);
    

    Hence you see the hasProperty matcher's additional information in this case but not while using containsInAnyOrder.

    I think the best you can do in this case is to implement a toString() for your MyBean class.

    There is already an issue reported about this: https://github.com/hamcrest/JavaHamcrest/issues/47