This tutorial explains software testing using the Hamcrest matcher framework.

1. Using Hamcrest matcher for unit tests

1.1. Purpose of the Hamcrest matcher framework

Hamcrest is a widely used framework for unit testing in the Java world.

Hamcrest target is to make your tests easier to write and read. For this, it provides additional matcher classes which can be used in test for example written with JUnit. You can also define custom matcher implementations.

To use Hamcrest matchers in your test you use the assertThat statement followed by one or several matchers.

The usage of Hamcrest matchers is demonstrated by the following code snippet.

package com.vogella.junit5.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.isA;

import org.junit.jupiter.api.Test;


class HamcrestInstanceOfTest {

    @Test
    void demosInstanceOfTest() {
        assertThat(Long.valueOf(1), instanceOf(Integer.class));
        // shortcut for instanceOf
        assertThat(Long.valueOf(1), isA(Integer.class));
    }

}

Hamcrest is typically viewed as a third generation matcher framework. The first generation used assert(logical statement) but such tests were not easily readable. The second generation introduced special methods for assertions, e.g., assertEquals(). This approach leads to lots of assert methods.

For example, the is method is a thin wrapper for equalTo(value).

package com.vogella.unittest.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

class HamcrestExampleTests {

    @Test
    void demoHamcrest() {
        boolean a = true;
        boolean b = true;

        assertThat(a, equalTo(b));
        assertThat(a, is(equalTo(b)));
        assertThat(a, is(b));
    }

}

It is also possible to chain matchers, via the anyOf or allOf method.

assertThat("test", anyOf(is("testing"), containsString("est")));

Hamcrest tries also to create readable error messages.

assertTrue(result instanceof String);
// error message:
java.lang.AssertionError
    at org.junit.Assert.fail(Assert.java:86)
    at org.junit.Assert.assertTrue(Assert.java:41)
    at org.junit.Assert.assertTrue(Assert.java:52)
// ...


assertEquals(String.class, result.getClass());
// error message:
java.lang.NullPointerException
    at com.vogella.hamcrest.HamcrestTest.test(HamcrestTest.java:30)
// ....


assertThat(result, instanceOf(String.class));
// error message:
java.lang.AssertionError:
Expected: an instance of java.lang.String
     but: null
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8)
// ...

2. Making the Hamcrest library available

To use Hamcrest in your Maven or Gradle project for you tests you have to add the dependencies to the test dependencies. You find the latest Hamcrest version on Maven central via https://search.maven.org/artifact/org.hamcrest/hamcrest.

The following contains the settings for Gradle and Maven.

Defining the Hamcrest dependency for Gradle

To use Hamcrest matchers for a project based on the Gradle build system, add the following dependencies to it.

dependencies {
    // more dependencies, e.g. for JUnit...
    testImplementation 'org.hamcrest:hamcrest:2.2'
}
Defining the Hamcrest dependency for Maven

Add the following dependency to your pom file to use Hamcrest for your tests in a Maven based project.

<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest</artifactId>
  <version>2.2</version>
  <scope>test</scope>
</dependency>

3. Using Hamcrest

3.1. Static import

To make all matchers available in your file add a static import. This also makes it easier to find matchers through code completion. You can add them to your test or configure your IDE to present them to you. You will later find an exercise for the Eclipse IDE.

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

3.2. Hamcrest matchers for lists

The usage of the Hamcrest matchers for lists are demonstrated by the following example.

com.vogella.unittest.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Every.everyItem;

import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;

class HamcrestListMatcherExampleTest {
    @Test
    void listShouldInitiallyBeEmpty() {
        List<Integer> list = Arrays.asList(5, 2, 4);

        assertThat(list, hasSize(3));

        // ensure the order is correct
        assertThat(list, contains(5, 2, 4));

        assertThat(list, containsInAnyOrder(2, 4, 5));

        assertThat(list, everyItem(greaterThan(1)));

    }
}
// Check that a list of objects has a property race and
// that the value is not ORC
assertThat(fellowship, everyItem(hasProperty("race", is(not((ORC))))));

3.3. Overview of Hamcrest matcher

The following are the most important Hamcrest matchers:

  • allOf - matches if all matchers match (short circuits)

  • anyOf - matches if any matchers match (short circuits)

  • not - matches if the wrapped matcher doesn’t match and vice

  • equalTo - test object equality using the equals method

  • is - decorator for equalTo to improve readability

  • hasToString - test Object.toString

  • instanceOf, isCompatibleType - test type

  • notNullValue, nullValue - test for null

  • sameInstance - test object identity

  • hasEntry, hasKey, hasValue - test a map contains an entry, key or value

  • hasItem, hasItems - test a collection contains elements

  • hasItemInArray - test an array contains an element

  • hasProperty - checks if a Java Bean has a certain property can also check the value of this property

  • closeTo - test floating point values are close to a given value

  • greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo

  • equalToIgnoringCase - test string equality ignoring case

  • equalToIgnoringWhiteSpace - test string equality ignoring differences in runs of whitespace

  • containsString, endsWith, startsWith - test string matching

To see all matchers, use API reference.

4. Exercise: Using Hamcrest matchers

Create or re-use a Maven or Gradle project named com.vogella.unittest.

4.1. Making the Hamcrest library available

To use Hamcrest in your Maven or Gradle project for you tests you have to add the dependencies to the test dependencies. You find the latest Hamcrest version on Maven central via https://search.maven.org/artifact/org.hamcrest/hamcrest.

The following contains the settings for Gradle and Maven.

Defining the Hamcrest dependency for Gradle

To use Hamcrest matchers for a project based on the Gradle build system, add the following dependencies to it.

dependencies {
    // more dependencies, e.g. for JUnit...
    testImplementation 'org.hamcrest:hamcrest:2.2'
}
Defining the Hamcrest dependency for Maven

Add the following dependency to your pom file to use Hamcrest for your tests in a Maven based project.

<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest</artifactId>
  <version>2.2</version>
  <scope>test</scope>
</dependency>

4.2. Configuring Eclipse to use the Hamcrest static imports

The Eclipse IDE cannot always create the corresponding static import statements automatically.

You can configure the Eclipse IDE to use code completion to insert typical JUnit method calls and to add the static import automatically. For this open the corresponding setting via Window  Preferences  Java  Editor  Content Assist  Favorites.

Use the New Type button to add the following entries to it:

  • org.hamcrest.MatcherAssert

  • org.hamcrest.Matchers

4.3. Using Hamcrests built-in matchers in unit tests

The target of this exercise is to make yourself familiar with Hamcrest matchers. The tests are trivial but you will get familiar with the usage of the Hamcrest matchers.

Navigate into the org.hamcrest.Matchers class to see available matchers.

Create the following new test class called HamcrestSimpleExamplesTests.

package com.vogella.unittest.hamcrest;

import static org.junit.jupiter.api.Assertions.fail;

import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class HamcrestSimpleExamplesTests {


    // tests for the list
    @DisplayName("Tests for the List")
    @Nested
    class ListTests {

        private List<Integer> list;

        @BeforeEach
        public void setup() {
            list = Arrays.asList(5, 2, 4);
        }

        @Test
        @DisplayName("List should have an intial size of 3")
        void ensureInitialSize() {
            fail();
        }

        @Test
        @DisplayName("Check content of the array")
        void containsNumbersInAnyOrder() {
            fail();
        }   private Integer[] ints;


        @Test
        void everyItemGreaterThan1() {
            fail();
        }
    }

    @DisplayName("Tests for the array")
    @Nested
    class ArrayTests {
        private Integer[] ints;

        @BeforeEach
        public void setup() {
            ints = new Integer[] { 7, 5, 12, 16 };

        }

        @Test
        void arrayHasSizeOf4() {
            fail();
        }

        @Test
        void arrayContainsNumbersInGivenOrder() {
            fail();
        }
    }

    @DisplayName("Tests for the Task")
    @Nested
    class TaskTests {

        // class to be tested
        public class Task {

            private final long id;
            private String summary;
            private String description;
            private int year;

            public Task(long id, String summary, String description) {
                this.id = id;
                this.summary = summary;
                this.description = description;
            }

            // getters/setters
        }

        // tests for the Task
        @Test
        void objectHasSummaryProperty() {
            Task task = new Task(1, "Learn Hamcrest", "Important");
            fail();
        }

        @Test
        void objectHasCorrectSummaryValue() {
            Task task = new Task(1, "Learn Hamcrest", "Important");
            fail();
        }

        @Test
        void objectHasSameProperties() {
            Task todo1 = new Task(1, "Learn Hamcrest", "Important");
            Task todo2 = new Task(1, "Learn Hamcrest", "Important");
            fail();
        }
    }

    @DisplayName("Tests for String")
    @Nested
    class StringTests {
        // tests for string
        @Test
        void ensureThatAnEmptryStringIsEmpty() {
            String input = "";
            fail();
        }

        @Test
        void ensureThatAStringIsEitherNullOrEmpty() {
            String input = "";
            fail();
        }
    }
}

4.4. Using Hamcrest collection matchers for lists

Implement the tests for the list. Ensure via individual tests with Hamcrest matchers that list:

  • has a size of 3

  • contains the elements 2, 4, 5 in any order

  • every item is greater than 1

Show Solution
package com.vogella.unittest.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;

import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

// more


// tests for the list
@DisplayName("Tests for the List")
@Nested
class ListTests {

    private List<Integer> list;

    @BeforeEach
    public void setup() {
        list = Arrays.asList(5, 2, 4);
    }


    @Test
    @DisplayName("List should have an intial size of 3")
    void ensureInitialSize() {
        assertThat(list, hasSize(3));
    }

    @Test
    @DisplayName("Check content of the array")
    void containsNumbersInAnyOrder() {
        assertThat(list, containsInAnyOrder(2, 4, 5));
    }

    @Test
    void everyItemGreaterThan1() {
        assertThat(list, everyItem(greaterThan(1)));
    }
}

4.4.1. Using Hamcrest collection matchers for arrays

Ensure via tests with Hamcrest matchers that the ints array

  • has a size of 4

  • contains 7, 5, 12, 16 in the given order

Show Solution
package com.vogella.unittest.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.arrayWithSize;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
//more

@DisplayName("Tests for the array")
@Nested
class ArrayTests {
    private Integer[] ints;

    @BeforeEach
    public void setup() {
        ints = new Integer[] { 7, 5, 12, 16 };

    }

    @Test
    void arrayHasSizeOf4() {
        assertThat(ints, arrayWithSize(4));
    }

    @Test
    void arrayContainsNumbersInGivenOrder() {
        assertThat(ints, arrayContaining(7, 5, 12, 16));
    }
}

4.4.2. Using Hamcrest beans matchers

Review the Task class, which will be used in your test.

Write tests that ensure that:

  • Task has a property called "summary"

  • If Task is constructed with the summary "Learn Hamcrest" that the summary property was initialized with this value

  • Two objects created with the same values, have the same property values

Show Solution

This test requires that you have generated / created getter and setter for the Task class. In Eclipse which can be done via Source  Generate Getter and Setters.

package com.vogella.unittest.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.samePropertyValuesAs;

import org.junit.jupiter.api.Test;

@DisplayName("Tests for the Task")
@Nested
class TaskTests {

    // class to be tested
    public class Task {

        public String getSummary() {
            return summary;
        }

        public void setSummary(String summary) {
            this.summary = summary;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public int getYear() {
            return year;
        }

        public void setYear(int year) {
            this.year = year;
        }

        public long getId() {
            return id;
        }

        private final long id;
        private String summary;
        private String description;
        private int year;

        public Task(long id, String summary, String description) {
            this.id = id;
            this.summary = summary;
            this.description = description;
        }

        // getters/setters
    }

    //more
    @Test
    void objectHasSummaryProperty() {
        Task task = new Task(1, "Learn Hamcrest", "Important");
        assertThat(task, hasProperty("summary"));
    }

    @Test
    void objectHasCorrectSummaryValue() {
        Task task = new Task(1, "Learn Hamcrest", "Important");
        assertThat(task, hasProperty("summary", equalTo("Learn Hamcrest")));
    }

    @Test
    void objectHasSameProperties() {
        Task todo1 = new Task(1, "Learn Hamcrest", "Important");
        Task todo2 = new Task(1, "Learn Hamcrest", "Important");

        assertThat(todo1, samePropertyValuesAs(todo2));
    }
}

4.4.3. Using Hamcrest String matchers

Write tests that ensure that:

  • "" is an empty string

  • a given string is either empty or null

Show Solution
package com.vogella.unittest.hamcrest;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

@DisplayName("Tests for String")
@Nested
class StringTests {
    @Test
    void ensureThatAnEmptryStringIsEmpty() {
        String input = "";
        assertThat(input, is(emptyString()));
    }

    @Test
    void ensureThatAStringIsEitherNullOrEmpty() {
        String input = "";
        assertThat(input, is(emptyOrNullString()));
    }
}

5. Exercise - Writing a custom Hamcrest matcher using FeatureMatcher

The target of this exercise is to write a custom matcher with Hamcrest.

5.1. Create Hamcrest Matchers

Define a custom matcher for Hamcrest which provides the length matcher for a String. We want to use the class FeatureMatcher. With FeatureMatcher we can wrap an existing Matcher, decide which field of the given Object under test to match and provide a nice error message. The constructor of FeatureMatcher takes the following arguments in this order:

  • The matcher we want to wrap

  • a description of the feature that we tested

  • a description of the possible mismatch

The only method we have to overwrite is featureValueOf(T actual) which returns the value which will get passed into the wrapped matches()/matchesSafely() method.

public static Matcher<String> length(Matcher<? super Integer> matcher) {
    return new FeatureMatcher<String, Integer>(matcher, "a String of length that", "length") {
        @Override
        protected Integer featureValueOf(String actual) {
            return actual.length();
        }
    };
}

5.2. Validate

Use your custom matcher in a test to check if the String "Gandalf" has a length of 8. Adjust the test if necessary.

5.3. Solutions for FeatureMatcher

Show Solution
@Test
public void fellowShipOfTheRingShouldContainer7() {
    assertThat("Gandalf", length(is(7)));
}
public static  Matcher<String> length(Matcher<? super Integer> matcher) {
    return new FeatureMatcher<String, Integer>(matcher, "a String of length that", "length") {
        @Override
        protected Integer featureValueOf(String actual) {
          return actual.length();
        }
    };
}

6. Exercise: Writing your custom Hamcrest matcher using TypeSafeMatcher

It is possible to write your custom Hamcrest matcher by extending TypeSafeMatcher. In contrast to BaseMatcher the TypeSafeMatcher class automatically checks for null values, checks the type and casts appropriately before delegating to matchesSafely(). It provides type safety by default. The following is an example for defining a matcher which allows testing if a String matches a regular expression.

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

public class RegexMatcher extends TypeSafeMatcher<String> {

    private final String regex;

    public RegexMatcher(final String regex) {
        this.regex = regex;
    }

    @Override
    public void describeTo(final Description description) {
        description.appendText("matches regular expression=`" + regex + "`");
    }

    @Override
    public boolean matchesSafely(final String string) {
        return string.matches(regex);
    }


     // matcher method you can call on this matcher class
    public static RegexMatcher matchesRegex(final String regex) {
        return new RegexMatcher(regex);
    }
}

The following snippet gives an example how to use it.

package com.vogella.android.testing.applicationtest;


import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;

public class TestCustomMatcher {

    @Test
    public void testRegularExpressionMatcher() throws Exception {
        String s ="aaabbbaaaa";
        assertThat(s, RegexMatcher.matchesRegex("a*b*a*"));
    }

}

7. Exercise: Combining matchers

Combining matchers is supported by Hamcrest out of the box but it has the limitation that the error is hard to read:

@Test
public void () {
    List<Integer> list = new ArrayList<>();
    assertThat(list, both(hasSize(1)).and(contains(42)));
}
Expected: (a collection with size <1> and iterable containing [<42>])
     but: a collection with size <1> collection size was <0>.

This not very readable.

7.1. Target

We want to write our own MatcherCombiner that provides us with a readable error message, even when multiple matchers fail.

7.2. Create MatchCombiner

We do this by inheriting from BaseMatch and by providing a starting method that let’s us chain matchers together. The matchers get saved in a list that we iterate over during the matching phase.

public class MatcherCombinator<T> extends BaseMatcher<T> {
    private final List<Matcher<? super T>> matchers = new ArrayList<>();
    private final List<Matcher<? super T>> failedMatchers = new ArrayList<>();

    private MatcherCombinator(final Matcher<? super T> matcher) {
        matchers.add(matcher);
    }

    public MatcherCombinator<T> and(final Matcher<? super T> matcher) {
        matchers.add(matcher);
        return this;
    }

    @Override
    public boolean matches(final Object item) {
        boolean matchesAllMatchers = true;
        for (final Matcher<? super T> matcher : matchers) {
            if (!matcher.matches(item)) {
                failedMatchers.add(matcher);
                matchesAllMatchers = false;
            }
        }
        return matchesAllMatchers;
    }

    @Override
    public void describeTo(final Description description) {
        description.appendValueList("\n", " " + "and" + "\n", "", matchers);
    }

    @Override
    public void describeMismatch(final Object item, final Description description) {
        description.appendText("\n");
        for (Iterator<Matcher<? super T>> iterator = failedMatchers.iterator(); iterator.hasNext();) {
            final Matcher<? super T> matcher = iterator.next();
            description.appendText("Expected: <");
            description.appendDescriptionOf(matcher).appendText(" but ");
            matcher.describeMismatch(item, description);
            if (iterator.hasNext()) {
                description.appendText(">\n");
            }
        }
    }

    public static <LHS> MatcherCombinator<LHS> matches(final Matcher<? super LHS> matcher) {
        return new MatcherCombinator<LHS>(matcher);
    }
}

To validate the implementation we write a new test.

@Test
public void test() {
    List<Integer> list = new ArrayList<>();
    assertThat(list, matches(hasSize(1)).and(contains(42)));
}
java.lang.AssertionError:
Expected:
<a collection with size <1>> and
<iterable containing [<42>]>
     but:
Expected: <a collection with size <1> but collection size was <0>>
Expected: <iterable containing [<42>] but No item matched: <42>.

You can adjust this output in the describeMismatch method.

8. Grouping your matchers for import

If you define many custom matchers it might become tedious to import them one by one into your test files. By grouping them into a single class you can import them with one statement. You can also group them together with Hamcrest matchers.

package com.vogella.hamcrest;
import com.vogella.hamcrest.matchers.RegexMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;

public class MyMatchers
{
    public static <T> Matcher<T> instanceOf(Class<T> target) {
        return Matchers.instanceOf(target);
    }

   public static Matcher<String> matchesRegex(String target) {
          return RegexMatcher.matchesRegex(target);
   }
}

In your test file:

import static com.vogella.hamcrest.MyMatchers.*;

9. Hamcrest assert statements compared to pure JUnit5 assert statements

The following snippets compare pure JUnit assert statements with Hamcrest matchers.

Junit5 Hamcrest

[source, java] ---- assertEquals(expected, actual); ----

[source, java] ---- assertThat(actual, is(equalTo(expected))); ----

assertNotEquals(expected, actual)

assertThat(actual, is(not(equalTo(expected))));

10. Hamcrest resources

11. Conclusion

You have learned to use the Hamcrest API to simplify writing and reading your unit tests.

The full implementation of all these examples and code snippets can be found in my Hamcrest project. The Hamcrest Maven examples are located in Hamcrest with Maven github project and the Gradle Hamcrest examples are located in Hamcrest with Gradle github project.