Hamcrest Compare Collections

Hamcrest compare collections

If you want to assert that the two lists are identical, don't complicate things with Hamcrest:

assertEquals(expectedList, actual.getList());

If you really intend to perform an order-insensitive comparison, you can call the containsInAnyOrder varargs method and provide values directly:

assertThat(actual.getList(), containsInAnyOrder("item1", "item2"));

(Assuming that your list is of String, rather than Agent, for this example.)

If you really want to call that same method with the contents of a List:

assertThat(actual.getList(), containsInAnyOrder(expectedList.toArray(new String[expectedList.size()]));

Without this, you're calling the method with a single argument and creating a Matcher that expects to match an Iterable where each element is a List. This can't be used to match a List.

That is, you can't match a List<Agent> with a Matcher<Iterable<List<Agent>>, which is what your code is attempting.

hamcrest - compare items in the collection

Your code compiles just fine in the first form with JDK 1.8.0_12, Hamcrest 1.3 and JUnit 4.12, although it does not produce the expected result due to a gotcha I'll be explaining below.

I can only guess that you may have a mix of library versions, or jdk, or something of the sorts. However, I believe it does not really matter, because of that gotcha I was mentioning.


Can anyone explain to me how hamcrest compares collections and and what the different methods from the library are for?

In short, you can either supply your own array/collection of matchers, or an array of items for which it will create matchers. Afterwards, the actual items are validated against the resulting matcher list.

If you check the sources you'll see that the IsIterableContainingInAnyOrder constructor accepts a collection of matchers:

public IsIterableContainingInAnyOrder(Collection<Matcher<? super T>> matchers) {
this.matchers = matchers;
}

... while the methods you were wondering about are factory methods which return an IsIterableContainingInAnyOrder instance. One is deprecated and I have skipped it. Then we have the following 2, where the first delegates to the second without anything fancy going on:

public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(Matcher<? super T>... itemMatchers) {
return containsInAnyOrder(Arrays.asList(itemMatchers));
}

public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(Collection<Matcher<? super T>> itemMatchers) {
return new IsIterableContainingInAnyOrder<T>(itemMatchers);
}

... and finally we have:

public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(T... items) {
List<Matcher<? super T>> matchers = new ArrayList<Matcher<? super T>>();
for (T item : items) {
matchers.add(equalTo(item));
}

return new IsIterableContainingInAnyOrder<T>(matchers);
}

As you see, a matcher is created for every item, which is somewhat a gotcha:

  • if you pass an array of arguments, you'll get a matcher for each item

assertThat(actual, containsInAnyOrder("one", "two", "four")); yelds:

java.lang.AssertionError: 
Expected: iterable over ["one", "two", "four"] in any order
but: No item matches: "four" in ["two", "one"]
  • if you pass a list, it'll count as a 1 argument array, and only 1 matcher will be created, for the list itself

assertThat(actual, containsInAnyOrder(Arrays.asList("one", "two", "four"))) yelds:

java.lang.AssertionError: 
Expected: iterable over [<[one, two, four]>] in any order
but: Not matched: "two"

Notice the subtle difference?


I found that it must be converted to the array in order to work (the
second assertion in the example):

Matchers.containsInAnyOrder(expected.toArray())); I suppose that in
this case this method from the library is used:containsInAnyOrder(T...
items)
, is that true ?

But how remaining methods from IsIterableContainingInAnyOrder can be
used ? Is there any way to compare collections without converting them
to the array?

Just use the inline form as it was intended: assertThat(myList, containsInAnyOrder("one", "two", "four")). I suppose this offers better readability and avoids unnecessary variables or superfluous coding, such as the expected collection. Usually you need to check for a few items, not hundreds, right?

Comparing two collections using hamcrest contains() method

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.

How to use hamcrest contains to compare 2 lists?

There is no overloaded contains method which takes a list of expected values.

In the statement assertThat(actualStrings, contains(expectedStrings))
the following method (in the Matchers class) is called:

<E> org.hamcrest.Matcher<java.lang.Iterable<? extends E>> contains(E... items)

Basically you are saying that you expect a list with one element and this element is expectedStrings but in fact it is expected1 (E is of type List<String> and not String). To verify add the following to the test which should then pass:

List<List<String>> listOfactualStrings = new ArrayList<>();
listOfactualStrings.add(actualStrings);
assertThat(listOfactualStrings, contains(expectedStrings));

To make the assertion work you have to convert the list to an array:

assertThat(actualStrings, contains(expectedStrings.toArray()));

Hamcrest equal collections

I cannot compare Arrays.asList and
Map.values with Hamcrest equals.

This is because of hamcrest's over-zealous type signatures. You can do this equality comparison, but you need to cast the List object to Collection before it'll compile.

I often have to do casting with Hamcrest, which feels wrong, but it's the only way to get it to compile sometimes.

Comparing lists with hamcrest

Chances are, that

expectedRecords.toArray()

would be converting it to an Object[], you can change it to using List.toArray​(T[] a)

expectedRecords.toArray(new GDSRecord[0])

and it should work.

JUnit hamcrest Long comparison

Actual return types

You are calling getContent() on return value Page<Long>. This will return a List<Long> as the docs state:

List<T> getContent()

Returns the page content as List.

Suitable matcher

So you can use Hamcrest's Matchers for Collections.
I would use hasItem() to check if the one item (e.g. user5.getId()) is contained in the list.

Matching the same types

But pay attention to the argument type you pass to hasItem(). The expected value should be of type Long or long (autoboxing), since your actual elements of List<Long> have type Long.

What is the return type of your argument user5.getId()?

The error-message shows you are expecting an int or Integer (because missing L for Long):

not matched: <132>

Whereas your List returns elements of Long (hence suffix L in the error-message):

iterable with items [<132L>]

Solution: cast or re-design

You can either cast the expected getId() to the required type Long:
assertThat(users.getContent(), containsInAnyOrder( (Long) user5.getId() ));

Or you bravely re-design the method under test to return Page<Integer>. Rational behind: since most IDs are implemented as Integer (see user5.getId()) all related methods (like findAllUserIdsWithLocationsWithoutCategories) should respect that type in their signature and return Integers.



Related Topics



Leave a reply



Submit