7

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

}
  • Might help to see your `Item` implementation. – user1071777 Aug 19 '14 at 13:52
  • 1
    You might want to define an equals method in Item. – user1071777 Aug 19 '14 at 13:58
  • 2
    So you expect your list of items to contain a list of items? Or do you expect your list of items to contain items? As far as I can see, `contains` is a varargs method not `Collection` based → `assertThat(expectedItems, contains(items.toArray()));`… – Holger Aug 19 '14 at 14:00
  • You're over thinking it. It should be `assertThat(actualItems, is(equalTo(expectedItems))`. From and answer by @jimjarret to a similar question: https://stackoverflow.com/a/46714949/1215301 – Coren Jul 25 '19 at 23:36

3 Answers3

4

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.

Community
  • 1
  • 1
Kevin Cruijssen
  • 9,153
  • 9
  • 61
  • 135
  • I added the above methods, but it still doesn't work. Gives me the same message. I have updated the post with the new definition of the class Item. –  Aug 21 '14 at 13:03
  • @I.K. I've made an edit to my answer, although I'm not entirely sure if this will fix your problem. – Kevin Cruijssen Aug 25 '14 at 08:07
  • actualItems contains an iphone and a skateboard. For the expectedItems, I manually insert an iPhone and skateboard as this is what I expect. Then I do the assertThat. I added the system out statements to make the post clear but I probably failed there. So two items should be in each collection. Thanks for trying. –  Aug 26 '14 at 15:53
  • @I.K. Ah k. Anyway, the contains doesn't work for an entire list if I'm not mistaken, so you should either check if each item individual is in the list with a for-loop, or you should use something like the `assertThat(actualList, is(expectedList));` to compare the entire lists with one another. – Kevin Cruijssen Aug 27 '14 at 07:48
4

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()));
Olimpiu POP
  • 5,001
  • 4
  • 34
  • 49
  • I used `containsAll` in the end. But I want to know how to use the Hamcrest library. It claims it can compare two collections but I am struggling to get it working. –  Aug 26 '14 at 15:49
  • @I.K. I get your point...so I added some extra suggestions including hamcrest matchers. Do they answer your question? – Olimpiu POP Aug 26 '14 at 20:57
  • Thanks. So I guess the natural question is, what good is hamcrest for when the junit library can handle comparing collections. What extra does hamcrest provide in general that junit does not? –  Aug 27 '14 at 15:49
  • As mentioned in my initial response, for this kind of assert using hamcrest doesn't have to obvious benefits. But its matchers are very powerful as you can on their documentation site. For instance imagine you want to check that every element of the list has a particular characteristic. By the way, if the response answered your question, marking as answered will help others as well. Thank you! – Olimpiu POP Aug 27 '14 at 17:18
0

You basically asserting that expectedItems is a list with one element and this element is expected to be a list itself with the two items iPhone and skateboard.

To assert that expectedItems and actualItems have the same elements in the same order try this:

@Test
public void getAllItems() {

    Collection<Item> actualItems = auction.getAllItems(joe);

    assertThat(actualItems, contains(iPhone, skateboard));
}

And beware that assertThat expects the "actual" object as first parameter and not the "expected".

Alternatively you can do something like that:

@Test
public void getAllItems() {

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

    assertThat(actualItems, contains(expectedItems.toArray(new Item[expectedItems.size()])));
}
eee
  • 3,241
  • 1
  • 17
  • 34