Search code examples
javahamcrest

Comparing two collections using hamcrest contains() method


I have two collections which I am trying to compare for equality in my unit tests, but I am struggling with the contains method. Here is what I have:

@Test
public void getAllItems() {

    Collection<Item> actualItems = auction.getAllItems(joe);
    Collection<Item> expectedItems = Lists.newArrayList();
    expectedItems.add(iPhone);
    expectedItems.add(skateboard);
    assertThat(expectedItems, contains(actualItems));

}

items contains the same objects as expectedItems so I would expect the assertion to be true but this is the output I get:

[Item{name=iPhone}, Item{name=Skateboard}]  --> Expected
[Item{name=iPhone}, Item{name=Skateboard}]  --> Actual

java.lang.AssertionError: 
Expected: iterable containing [<[Item{name=iPhone}, Item{name=Skateboard}]>]
     but: item 0: was <Item{name=iPhone}>
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8)

Please can you help me where I am going wrong with using the contains method?

public class Item {

    private String name;

    public Item(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return Objects.toStringHelper(this).add("name", name).toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Item other = (Item) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

Solution

  • A Collection's .contains(...) uses the equals and hashCode methods of the Objects. In order to use equals (or in this case contains) on your own Objects, you need to override the equals and hashCode methods of your class. This is because Java uses references behind the scenes, so even though the field may be equal, the Object-references are not.

    In Eclipse you can generate them using right-mouse click -> Source -> Generate hashCode() and equals().... But, since you never stated you use Eclipse, here is an example of the methods that are generated:

    // Overriding this class' equals and hashCode methods for Object comparing purposes 
    // using the Collection's contains
    // contains does the following behind the scenes: Check if both inputs aren't null, 
    // check if the HashCodes match, check if the Objects are equal.
    // Therefore to use the Collection's contains for Objects with the same fields, we
    // need to override the Object's equals and hashCode methods
    // These methods below are generated by Eclipse itself using "Source -> Generate 
    // hashCode() and equals()..."
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj)
            return true;
        if(obj == null)
            return false;
        if(getClass() != obj.getClass())
            return false;
        Item other = (Item) obj;
        if(name == null){
            if(other.name != null)
                return false;
        }
        else if(!name.equals(other.name))
            return false;
        return true;
    }
    

    If you add both of these to your Item-class, the contains will work.


    EDIT:

    I'm not sure, but when I look at your code I think the following might be wrong:

    @Test
    public void getAllItems() {
    
        Collection<Item> actualItems = auction.getAllItems(joe);
        Collection<Item> expectedItems = Lists.newArrayList();
    
        // You first print both lists
        System.out.println(expectedItems);
        System.out.println(items);
    
        // And then add the two items to the expectedItems
        expectedItems.add(iPhone);
        expectedItems.add(skateboard);
        assertThat(expectedItems, contains(actualItems));
    }
    

    If you try the following instead:

    @Test
    public void getAllItems() {
    
        Collection<Item> actualItems = auction.getAllItems(joe);
        Collection<Item> expectedItems = Lists.newArrayList();
    
        // First add both items
        expectedItems.add(iPhone);
        expectedItems.add(skateboard);
    
        // Then print both lists
        System.out.println(expectedItems);
        System.out.println(items);
    
        assertThat(expectedItems, contains(actualItems));
    }
    

    Does the expectedList now contain 4 items?

    [Item{name=iPhone}, Item{name=Skateboard}, Item{name=iPhone}, Item{name=Skateboard}]  --> Expected
    [Item{name=iPhone}, Item{name=Skateboard}]  --> Actual
    

    In that case you shouldn't add the two items, since they are already present in the list.

    Also, you're trying to use the contains on the entire list. Normally the contains is used to see if a single item is present in the list. So you could either use something like this:

    for(Item i : expectedList){
        assertTrue(actualList.contains(i));
    }
    

    or perhaps something like this, in case you use these libraries:

    assertThat(actualList, is(expectedList));
    

    I'm not sure if this is the cause and if this will fix it, since you use a different JUnit library then I usually do and I'm not sure if these syntax with the Asserts are possible.