Skip to main content

On the subtle uses of Hamcrest tests

Posted by johnsmart on April 1, 2008 at 7:41 PM PDT

I came across an interesting issue with the Hamcrest asserts today. I have a method that returns a list of domain objects, as shown here:

    List<Stakeholder> stakeholders = stakeholderManager.findByName("Telecom");

This is a good example of how Hamcrest can make testing with collections easier. For example, to test the findByName() method, you should be able to something like this:

    @Test
    public void should_find_embedded_search_term_at_start() {
        List<Stakeholder> stakeholders  = stakeholderManager.findByName("Health");
        assertThat(stakeholders, hasItem(hasProperty("name",is("Health Associates"))));
    }

This Hamcrest assert checks that the list contains at least one Stakeholder object whose name is "Health Associates". This is clearly much nicer than having to iterate through the result list and test each Stakeholder object individually.

Unfortunatly, this doesn't work: you get a compilation error along the following lines:

The method assertThat(T, Matcher) in the type MatcherAssert is not applicable for the arguments (List<Stakeholder>, Matcher>)

Indeed, Hamcrest seems to be having trouble with the idea that a List of Stakeholders is also a List of Objects.

So can't we just cast the result (a List object) to a List? The short answer is no. For type-safety reasons that are well-documented elsewhere, you can't cast a List to a List. If you could do this, you could add objects that weren't Stakeholders to the list. However, you can find a work-around fairly easily. But this is where the details get rather interesting. For example, the following seems to compile in Eclipse, but nowhere else:
        List stakeholders  = stakeholderManager.findByName("Health");
        assertThat(stakeholders, hasItem(hasProperty("name",is("Health Associates"))));

Outside Eclipse, this will fail with an error along the following lines:
StakeholderManagerTest.java:[62,8] cannot find symbol 
symbol  : method assertThat(java.util.List,org.hamcrest.Matcher>)

My friend Eduard Letifov pointed out the following solution, which will work both within Eclipse and using a normal JDK:

        List stakeholders  = stakeholderManager.findByName("Health");
        List<Object> listOfStakeholders  = stakeholderManager.findByName("Health");
        assertThat(listOfStakeholders, hasItem(hasProperty("name",is("Health Associates"))));

Which you can simplify to the following:
        List stakeholders  = stakeholderManager.findByName("Health");
        assertThat((List<Object>) stakeholders, hasItem(hasProperty("name",is("Health Associates"))));

That's right, there are two casts here: one from List to List, and a second from List to List. There's sure to be a more elegant way, but it probably involves modifying the signature of the Hamcrest methods. So this will do for now.

Until next time...

Related Topics >>

Comments

Thanks, that's a nice solution, Neil.

Sorry java.net sucked up my angle braces. The code sample should be:

Matcher<Stakeholder> withHealthAssociatesName = hasProperty("name", is("Health Associates")); assertThat(stakeholders, hasItem(withHealthAssociatesName));

Hi John, sorry you're having trouble with Hamcrest. There is another quick fix which might be clearer.

hasProperty has a captured type so you can do something like:

Matcher withHealthAssociatesName = hasProperty("name", is("Health Associates")); assertThat(stakeholders, hasItem(withHealthAssociatesName));

I generally pull out my own static factory methods for matchers in cases like this.

There is a Java language proposal that would make your code work but for now you'll have to choose one of these options.

We've had a few issues trying to suite everybody's needs with regards to generics. I'm working on getting a 1.2 release out which should be more flexible.