Make your Tests more readable with custom Hamcrest Matchers

October 28th, 2012 by

Everyday we’re writing tests for our software and sometimes we’re in a situation where we’re testing a specific type or object very often.

Luckily Hamcrest allows us to create custom matchers by subclassing from a given variety of available matchers.

 

Adding jUnit and Hamcrest

First add the dependencies for JUniti and Hamcrest to your project’s pom.xml or alternative build system.

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.10</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-all</artifactId>
        <version>1.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Creating the Matcher

First we need a class that we’re going to create a custom matcher for .. let’s say a book .. and books have id’s, a title and an ISBN number:

package com.hascode.entity;
 
public class Book {
 private final Long id;
 private final String isbn;
 private final String title;
 
 public Book(final Long id, final String isbn, final String title) {
 this.id = id;
 this.isbn = isbn;
 this.title = title;
 }
 
 public Long getId() {
 return id;
 }
 
 public String getIsbn() {
 return isbn;
 }
 
 public String getTitle() {
 return title;
 }
 
 @Override
 public String toString() {
 return "Book [id=" + id + ", isbn=" + isbn + ", title=" + title + "]";
 }
}

Now we want to add matchers that can be applied when we want to test a book somewhere .. we’re putting them into static methods in a wrapper class so that we’re able to use a static import in our test case later.

One matcher is applied to compare a book’s ISBN number and the other matcher tests the book’s id.

We’re simply subclassing Hamcrest’s TypeSafeMatcher here – for a full list of the different matchers available, please take a look at API docs.

package com.hascode.matcher;
 
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
 
import com.hascode.entity.Book;
 
public class BookMatcher {
 public static Matcher<Book> hasIsbn(final String isbn) {
 return new TypeSafeMatcher<Book>() {
 
 @Override
 public void describeTo(final Description description) {
 description.appendText("expected result from getIsbn(): ")
 .appendValue(isbn);
 }
 
 @Override
 public boolean matchesSafely(final Book book) {
 return isbn.equals(book.getIsbn());
 }
 
 @Override
 public void describeMismatchSafely(final Book book,
 final Description mismatchDescription) {
 mismatchDescription.appendText("was ").appendValue(
 book.getIsbn());
 }
 
 };
 }
 
 public static Matcher<Book> hasId(final Long id) {
 return new TypeSafeMatcher<Book>() {
 
 @Override
 public void describeTo(final Description description) {
 description.appendText("expected result from getId(): ")
 .appendValue(id);
 }
 
 @Override
 protected boolean matchesSafely(final Book book) {
 return id == book.getId();
 }
 
 @Override
 public void describeMismatchSafely(final Book book,
 final Description mismatchDescription) {
 mismatchDescription.appendText("was ")
 .appendValue(book.getId());
 }
 };
 }
}

Using the Matchers in a Test

Now that we’ve got shiny new matchers we should use them in a test…

package com.hascode.entity;
 
import static com.hascode.matcher.BookMatcher.hasId;
import static com.hascode.matcher.BookMatcher.hasIsbn;
import static org.hamcrest.MatcherAssert.assertThat;
 
import org.junit.Test;
 
public class BookTest {
 @Test
 public void testBookIsbn() {
 Book book = new Book(1l, "5555", "A book");
 assertThat(book, hasIsbn("1234"));
 }
 
 @Test
 public void testBookId() {
 Book book = new Book(23l, "5555", "A book");
 assertThat(book, hasId(3l));
 }
}
Test Result from the JUnit Runner in Eclipse

Test Result from the JUnit Runner in Eclipse

All test results in the Problems View

All test results in the Problems View

If you want some more detailed information about this topic I’d recommend the following excellent article from Adrian Elsener posted on planetgeek.ch.

Troubleshooting

  • It is important to load the Hamcrest dependencies before the JUnit dependencies because JUnit comes with a custom version of Hamcrest .. and this version does not work as intended .. so be careful to use org.hamcrest.MatcherAssert.assertThat instead of org.junit.Assert.assertThat. If you’re using JUnit 4.11 (or above) this shouldn’t be a problem anymore because a newer version of Hamcrest is shipped (1.3 with JUnit 4.11)..

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Mercurial:

hg clone https://bitbucket.org/hascode/hamcrest-matcher-sample

Resources

Tags: , , , ,

6 Responses to “Make your Tests more readable with custom Hamcrest Matchers”

  1. David Says:

    What a horrible syntax highlighting…

  2. micha kops Says:

    You’re welcome.

  3. Vahid Says:

    Thank you for sharing, but I’m quite sure most of visitors leave your page after a while just because of the page style.

  4. micha kops Says:

    Valid point :)

  5. Phil 'Animal' Andrews Says:

    Thanks for the code example which I found very useful. I like the way you used the anonymous technique rather than explicitly extending the TypeSafeMatcher class.

    I am not a great fan of your page style but if that is your preference then the other readers should be quiet and, rather than criticise you, they should thank you for your efforts in helping the Java Dev community which is tragically lacking in unit test skills.

    This is really helpful.

    Good luck.

  6. micha kops Says:

    Thanks, you’re welcome :)

Search
Categories