RSS Feed Subscribe to RSS Feed

 

Hamcrest

Hamcrest is a framework for writing matcher objects. Matchers have a variety of uses, but are particularly useful when writing unit tests. Instead of using JUnit’s assertEquals methods, we use Hamcrest’s assertThat construct with one (or more) of the many Matchers available. For example

    assertTrue(a.equalTo(b));

becomes

    assertThat(a, equalTo(b));

A small change in this example, but Hamcrest’s benefits are many, enabling you to write much more flexible tests that are easier to read and have more meaningful failure messages.

    Benefits of using Hamcrest
        Improved readability of tests
        Better failure messages
        Combine Matchers
        Custom Matchers
    Limitations
        Don’t use if JUnit will do
        Finding the right Matcher
    Setup
    Trouble Shooting
    Links


Benefits of using Hamcrest

Improved readability of tests

The following code, which utilizes the Hamcrest API, is easy to understand, even if you have no understanding of Hamcrest, or Java for that matter:


    assertThat(car, is(equalTo(ferrari));
    assertThat(cars, hasItems(ferarri, porsche));

Basically, Hamcrest provides a fluent API.


Better failure messages

This is one of the biggest advantages of using Hamcrest.
For example, if you run the following test, which makes use of JUnit’s assertTrue method:

	
    @Test
    public void testGreatThan_JUnit() {
        int minimumPrice = 0;
        int actualPrice = -1;
        //using org.junit.Assert.assertTrue
        assertTrue(actualPrice > minimumPrice);
    }

You get the following terse and uninformative error:

java.lang.AssertionError

However if you rewrite the test to use Hamcrest’s assertThat method with the greaterThan matcher, as follows:

	
    @Test
    public void testGreaterThan_Hamcrest() {    
        int minimumPrice = 0;
        int actualPrice = -1;
        //using org.hamcrest.MatcherAssert.assertThat 
        //and  org.hamcrest.Matchers.greaterThan
        assertThat(actualPrice, greaterThan(minimumPrice));
    }

You would get the following much more informative error message:

java.lang.AssertionError:
Expected: a value greater than <0>
but: <-1> was less than <0>

Basically, assertThat will tell you what was expected and what the value actually was where as assertTrue will only tell you that you got false where you expected true.
(You can of course put an explicit error message in the JUnit assertion yourself of course, as in:
assertTrue(“Expected a value greater than ” + minimumPrice, actualPrice > minimumPrice);
But Hamcrest is more descriptive and avoids the extra effort.)

Combinations of Matchers

Hamcrest allows you to use weird and wonderful combinations of Matchers. The obvious example is using not(), but other combinations can be achieved using Matchers such as both(), either(), or(), anyOf(), and many more.

Create your own Matchers

You can also create your own custom Matchers too, by implementing the Matcher interface. I wrote a blog post with an example of a custom matcher, but you can also find good examples here and here.

Finally, as a wrap up to the benefits of using Hamcrest, see the JUnit release notes which includes a description of using the Matcher mechanism.

Limitations


Don’t use if JUnit will do

If there is already a suitable JUnit method to use, there may not be any benefit to using a Hamcrest Matcher. Specifically, if you are just comparing primitive or String values, JUnit’s overloaded assertEquals methods should be adequate. For example, the follow two test approaches are both fairly easy to read and give the same message on test failure:


	@Test
    	public void testEquals_JUnit() {
	    int expectedPrice = 0;
	    int actualPrice = -1;
	    //using org.junit.Assert.assertEquals
	    assertEquals(expectedPrice, actualPrice);
	    //java.lang.AssertionError: expected:<0> but was:<-1>
	}

	@Test
	public void testEquals_Hamcrest() {
	    int expectedPrice = 0;
	    int actualPrice = -1;
	    //using org.hamcrest.MatcherAssert.assertThat 
            //and org.hamcrest.Matchers.equalTo
	    assertThat(actualPrice, equalTo(expectedPrice));
	    //java.lang.AssertionError: Expected: <0> but: was <-1>
	}

However, for anything more complicated than primitive or String comparison, Hamcrest’s Matchers are likely to be much more useful.


Finding the right Matcher

One of the issues I have with Hamcrest is that the matchers are not all available in one place. It makes finding the correct matcher difficult and also means your IDE’s code assist, or import organizer, is not likely to be much help either. Most matchers are accessible via the Matchers class (although confusingly there is also the CoreMatchers class, which seems to be a subset – I never quite figured that one out…). But other matchers are located in other classes and packages.

Example 1
The very useful hasItem matcher is available in the aforementioned Matchers class:


    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.Matchers.hasItem;

    public class HamcrestExamples {

        List list = new ArrayList();
	
	@Before
	public void setup() {
		list.add("itemA");
		list.add("itemB");
	}
	
	@Test
	public void test() {
		assertThat(list, hasItem("itemA"));
	}
}

But if you want to use the very similar hasItems matcher, you need to know (or trawl the javadocs to find out) it is in the IsCollectionContaining class.


    import static org.hamcrest.core.IsCollectionContaining.hasItems;
    ...	
        @Test
	public void testHasItems() {
		assertThat(list, hasItems("itemA", "itemB"));
	}

Example 2
There is a useful containsString() method in the Matchers class that returns a Matcher to check is a String contains a sub-String, but if you want to check if a String contains several sub-Strings, in order, you need to know to look at the StringContainsInOrder class.

Notes:

  • It looks like the above hasItems example will be remedied in 1.3 where hasItems will be made available via the Matchers class.
  • There is a very similar testing library that seems to overcome this problem called Fluent Assertions module from FEST. FEST has all the matchers available via a single static import. (Also, see this interesting comparison with Hamcrest – I see no reason not to use both)


Setup

  1. Download the Hamcrest jar(s) from http://code.google.com/p/hamcrest/downloads (using the all-in-one Jar is easiest)

    Note that JUnit does ship with some Hamcrest classes included. However, I prefer to use the Hamcrest jars directly because

    • JUnit only includes the core Hamcrest (hamcrest-core) API classes. A much larger library of Matcher implementations is available by also using the hamcrest-library jars. Personally, I simply use the hamcrest-all.jar, which includes all the available Hamcrest classes.
    • The package names in JUnit are different than those in Hamcrest e.g. org.hamcrest.MatcherAssert.assertThat versus org.junit.Assert.assertThat
      I prefer to keep things simply by always referring to the native org.hamcrest packages directly.

    If you do choose to use the Hamcrest jars directly, it is also advisable to use the junit-dep jars. These come without the Hamcrest classes included, avoiding any uncertainty about which Hamcrest classes you are actually using.

  2. Add the Hamcrest jar(s) to your classpath or maven dependencies.
  3. Statically import Hamcrest’s assertThat construct and the Hamcrest matchers:

    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.Matchers.*;

  4. Start using Hamrests’s assertThat in your unit tests, instead of JUnit’s assertTrue and assertEquals etc
    e.g. instead of

    
        assertEquals(expectedValue, actualValue);
    

    use

    
        assertThat(actualValue, equalTo(expectedValue));
    

Perhaps in future postings I will talk about how to use Hamcrest’s Matchers with Mock object frameworks such as EasyMock and JMock.


Trouble Shooting

Error:

java.lang.SecurityException: class “org.hamcrest.Matchers”‘s signer information does not match signer information of other classes in the same package

You simply need to make sure the Hamcrest jar comes before JUnit in your classpath. Alternatively, use junit-dep.

Error:


cannot find symbol method
assertThat(java.util.List,org.hamcrest.Matcher>)

or


cannot find symbol method 
assertThat(java.util.List,org.hamcrest.Matcher>)

This error can happen with any iterable (not just Lists, as in the above example) using hasItem or hasItems (and possibly other matchers too). It is a known issue with Hamcrest at the moment and is documented here, along with some fixes. I took the not-pretty-but-works option of declaring the Matcher in a local variable first
e.g.


    assertThat(list, hasItems(item1, item2));

becomes


    Matcher> matcher = hasItems(item1, item2);
    assertThat(list, matcher);

Like I said, not-pretty-but-works.


Links

Tags: , ,

2 Responses to “Hamcrest”

  1. Nikolay |

    Thank you for the review, and especially for pointing out that bug about hasItems.

    I was desperately trying to find the solution for days.

  2. sabram |

    Hi Nikolay,
    Thanks for commenting. Glad you found the post useful.

    I got a little frustrated with Hamcrest and use FEST these days instead, although I admittedly haven’t looked into the latest 1.3 release of Hamcrest in detail.

    Shaun

Leave a Reply