views:

265

answers:

2

I'm just wondering how folks unit test and assert that the "expected" collection is the same/similar as the "actual" collection (order is not important).

To perform this assertion, I wrote my simple assert API:-

public void assertCollection(Collection<?> expectedCollection, Collection<?> actualCollection) {
    assertNotNull(expectedCollection);
    assertNotNull(actualCollection);
    assertEquals(expectedCollection.size(), actualCollection.size());
    assertTrue(expectedCollection.containsAll(actualCollection));
    assertTrue(actualCollection.containsAll(expectedCollection));
}

Well, it works. It's pretty simple if I'm asserting just bunch of Integers or Strings. It can also be pretty painful if I'm trying to assert a collection of Hibernate domains, say for example. The collection.containsAll(..) relies on the equals(..) to perform the check, but I always override the equals(..) in my Hibernate domains to check only the business keys (which is the best practice stated in the Hibernate website) and not all the fields of that domain. Sure, it makes sense to check just against the business keys, but there are times I really want to make sure all the fields are correct, not just the business keys (for example, new data entry record). So, in this case, I can't mess around with the domain.equals(..) and it almost seems like I need to implement some comparators for just unit testing purposes instead of relying on collection.containsAll(..).

Are there some testing libraries I could leverage here? How do you test your collection?

Thanks.

+4  A: 

If the equals method doesn't check all the fields, you can use the Unitils http://unitils.org/ ReflectionAssert class. Calling

ReflectionAssert.assertReflectionEquals(expectedCollection,actualCollection)

will compare each element reflectively field by field (and this doesn't just apply for collections, it will work for any object).

Jeff Storey
@jeff: this seems like a good way for my collection assertion. If A extends B, I assume it will take account of fields from both A and B, is that correct? I can't find it in the API documentation, but I suppose I can test it out. Is there a way to specify a rule to assert only the fields from A and not B?
limc
@jeff: okay, this is pretty awesome... just read up the documentation, and it seems like I should be using ReflectionAssert.assertLenientEquals(..) since that doesn't take account of the item order. Thanks much.
limc
@limc. You're welcome. I'm not sure on a way to just check the subclass fields (it may be possible, I've just never tried).
Jeff Storey
+3  A: 

I'm not sure what version of JUnit you're using, but recent ones have an assertThat method which takes a Hamcrest Matcher as an argument. They're composable so you can build up complex assertions about a collection.

For instance, if you wanted to assert that a collection A contained every element in collection B, you could write:

import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import org.hamcrest.beans.SamePropertyValuesAs;

public class CollectionTests {

    /*
    * Tests that a contains every element in b (using the equals()
    * method of each element) and that a has the same size as b.
    */
    @Test
    public void test() {
        Collection<Foo> a = doSomething();
        Collection<Foo> b = expectedAnswer;

        assertThat(a, both(hasItems(b)).and(hasSize(b.size())));
    }

    /*
    * Tests that a contains every element in b (using introspection
    * to compare bean properties) and that a has the same size as b.
    */
    @Test
    public void testBeans() {
        Collection<Foo> a = doSomething();
        Collection<Foo> b = expectedAnswer;
        Collection<Matcher<Foo>> bBeanMatchers =
          new LinkedList<Matcher<Foo>>();

        // create a matcher that checks for the property values of each Foo
        for(Foo foo: B)
            bBeanMatchers.add(new SamePropertyValuesAs(foo));

        assertThat(a, both(hasItems(bBeanMatchers)).and(hasSize(b.size())))
    }
}

The first test just uses the equalTo() matcher on every object (which will delegate to your equals implementation). If that's not strong enough, you can use the second case, which will use getters and setters to compare every element. Finally, you can even write your own matchers. The Hamcrest package doesn't come with a matcher for matching by field (as opposed to matching bean properties), but it's trivial to write a FieldMatcher (and indeed is a good exercise).

The Matchers are a bit odd at first, but if you follow their example of making new Matchers have a static method that returns the matcher you can do a bunch of import statics and your code basically reads like an English sentence ("assert that a both has the items in b and has the same size as b"). You can build up a pretty impressive DSL with these things and make your test code a lot more elegant.

jasonmp85
@jasonmp85: Thanks for the info. I guess I never realized these actually exist. :) But I don't think it will work for me in my current scenario. I just read up the documentation, and it seems like equalTo() tests object equality using Object.equals, and in my case, I don't want to futz around with my equals(..), if possible. But I'll keep this useful reference in mind for future use.
limc
Right, the equalTo() matcher uses equals(), but the SamePropertyValuesAs matches all the getters and setters of two JavaBeans. However, if you need to match private fields or something, you'll have to roll your own if you go this route.
jasonmp85