Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing two collections using hamcrest contains() method

Tags:

java

hamcrest

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;
    }

}
like image 250
M.K. Avatar asked Aug 19 '14 13:08

M.K.


2 Answers

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.

like image 67
Kevin Cruijssen Avatar answered Sep 23 '22 06:09

Kevin Cruijssen


I really don't think you actually need hamcrest for this. Wouldn't it be easier to make the asserts in one of the following ways:

A list is still an object at the end of the day:

org.junit.Assert.assertEquals(expected, actual)

An old fashion functionality for lists by using containsAll(..):

org.junit.Assert.assertTrue(expectedItems.containsAll(actualItems))

Using asserts for arrays' equality:

org.junit.Assert.assertArrayEquals(expectedItems.toArray(), actualItems.toArray())

Of course you can use hamcrest as well:

org.hamcrest.MatcherAssert.assertThat(actual, Matchers.containsInAnyOrder(actual.toArray()));

OR

org.hamcrest.MatcherAssert.assertThat(actual, Matchers.contains(actual.toArray()));
like image 33
Olimpiu POP Avatar answered Sep 20 '22 06:09

Olimpiu POP