How to use Hamcrest with Junit5

Hamcrest is a matcher framework as they stated. It has implementations for several languages also. Let’s look at how to use Hamcrest with Junit5 to write more readable self-explanatory assertions in unit tests.

Setting Up Project

Let’s use Junit5 BOM as we previously did. Starting from Hamcrest version 2, there is only one artifact is available. If you are using Junit4 with Hamcrest2, you may have to get your dependencies correct as suggested here.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.aptkode</groupId>
    <artifactId>unit-test-series</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <allure-maven.version>2.10.0</allure-maven.version>
        <allure-junit5.version>2.13.0</allure-junit5.version>
        <aspectj.version>1.9.1</aspectj.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>5.7.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>13</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>
</project>

First Assertion Using Hamcrest with Junit5

Let’s assume we have a person, let’s assert his age is 25.

@Test
void beanPropertyTest() {
    Person jhon = new Person("jhon");
    assertThat(jhon, hasProperty("name", is("jhon")));
}

Why Hamcrest with Junit5 produce more declarative tests?

We have been using assertEquals method for decades. Do you remember the parameters? Which one is the expected parameter, is it the first one? I’m getting lost each time, with the help of the newer IDEs its not much of a problem. Let’s assume we have a new engineer in our team. At first, he needs to understand the test and context before identifying what assertEquals. Let’s have a look at following Junit5 test.

@Test
void beanPropertyTest() {
    Person person = new Person("jhon");
    assertEquals("jhon", person.getName());
}

Let’s write it using Hamcrest.

@Test
void beanPropertyTest() {
    Person person = new Person("jhon");
    assertThat(person.getName(), is("jhon"));
}

It’s more readable now. Isn’t it? Also Let’s look at error messages produced by two assertion methodologies.

Junit Assertion Failure Message
Hamcrest Assertion Failure Message

If you want it to more redable, you can use describeAs wrapper method to format the message as you want. Have a look at following test case. You could also use variables to form a templated message.

@Test
void beanPropertyTest() {
    Person person = new Person("mike");
    assertThat(person.getName(), describedAs("person name is jhon",is("jhon")));
}

Above test failure produces following error message.

Hamcrest descrieAs method in action

I think now you can decide what produces more readable tests.

Next, we’ll have a look at various scenarios that Hamcrest produce more readable assertions. Also you can jump into the video tutorial if you are a visual and audio learner.

Aptkode – Unit Testing Novice to Savvy Series – Using Hamcrest with Junit5

The Hamcrest Cheat List

Assert Java bean has a property with a value

@Test
void beanPropertyTest() {
    Person jhon = new Person("jhon");
    assertThat(jhon, hasProperty("name", is("jhon")));
}

Verify variable is a particular type

@Test
void typeTest() {
    Person jhon = new Person("jhon");
    assertThat(jhon.getName(), isA(String.class));
    assertThat(jhon.getName(), instanceOf(String.class));
}

Assert variable is holding an exact value

@Test
void valueTest() {
    Person jhon = new Person("jhon");
    assertThat(jhon.getName(), is("jhon"));
}

Check something is in an array

@Test
void isInArrayTest() {
    Integer[] numbers = new Integer[]{1, 2, 3, 4, 5};
    assertThat(2, in(numbers));
    assertThat(numbers, hasItemInArray(2));
    assertThat(2, oneOf(numbers));
}

Verify something is in a list

@Test
void isInListTest() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    assertThat(2, in(numbers));
    assertThat(numbers, hasItem(2));
}

Check size of a collection with bound checking

@Test
void listHasSizeTest() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    assertThat(numbers, hasSize(5));
    // assert upper bound
    assertThat(numbers, hasSize(lessThan(10)));
    // assert lower bound
    assertThat(numbers, hasSize(greaterThan(4)));
}

Assert map has a certain key

@Test
void mapHasKeyTest() {
    Map<String, Integer> numbers = Stream.of(1, 2, 3, 4, 5)
            .collect(Collectors.toMap(String::valueOf, v -> v));
    assertThat(numbers, hasKey("1"));
    // assert key with non null value
    assertThat(numbers, hasEntry(is("1"), notNullValue()));
}

Assert map has a certain value

@Test
void mapHasValueTest() {
    Map<String, Integer> numbers = Stream.of(1, 2, 3, 4, 5)
            .collect(Collectors.toMap(String::valueOf, v -> v));
    assertThat(numbers, hasValue(2));
}

Validate that list has values in exact order

@Test
void listHasItemsInOrderTest() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    assertThat(numbers, contains(1, 2, 3, 4, 5));
}

Assert that list has values in any order

@Test
void listHasItemsInAnyOrderTest() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    assertThat(numbers, containsInAnyOrder(5, 4, 3, 2, 1));
}

Assert a collection has items in relative order

@Test
void listHasItemRelativeOrderTest() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    assertThat(numbers, containsInRelativeOrder(3, 4));
}

Check string ends with something

@Test
void stringEndsWithTest() {
    assertThat("aptkode.com", endsWith("com"));
    assertThat("aptkode.com", endsWithIgnoringCase("COM"));
}

Assert string contains something

@Test
void stringContainsTest() {
    assertThat("aptkode.com", containsString("kode"));
    assertThat("aptkode.com", containsStringIgnoringCase("KODE"));
}

Check string starts with something

@Test
void stringStartsWithTest() {
    assertThat("aptkode.com", startsWith("apt"));
    assertThat("aptkode.com", startsWithIgnoringCase("APT"));
}

Assert bounds of a number like less than, greater than and so on

@Test
void assertBoundsTest() {
    assertThat(1, lessThan(2));
    assertThat(5, lessThanOrEqualTo(5));
    assertThat(2, greaterThan(1));
    assertThat(5, greaterThanOrEqualTo(5));
}

Assert monetize values more declaratively using big decimal matchers

@Test
void assertMoneyTest() {
    assertThat(new BigDecimal("102.4"), closeTo(new BigDecimal(100), new BigDecimal("2.5")));
}

Negate assertions in style

@Test
void negateAssertionsTest() {
    assertThat("aptkode.com", not(startsWith("google")));
    assertThat(5, not(in(Arrays.asList(1, 2, 3))));
}

Combine assertions with operators like logical or and logical and

@Test
void combineAssertionsTest(){
    assertThat("aptkode.com", both(startsWith("apt")).and(endsWith("com")));
    assertThat("aptkode", either(startsWith("apt")).or(startsWithIgnoringCase("APT")));
}

Make sure multiple assertions met in one go

@Test
void allAssertionsTest(){
    assertThat("aptkode.com", allOf(startsWith("apt"), endsWith("com"), containsString("kode")));
}

Make sure one of the assertions are met

@Test
void anyAssertionsTest(){
    assertThat("aptkode.com", anyOf(startsWith("com"), containsString("bla"), startsWith("apt")));
}

Extendability of Hamcrest

The most valuable thing with Hamcrest is its extendability. We could write our matcher which add declarative assertions system for a particular domain. We’ll explore how we can do it in a later article. Also there are lot of community-driven projects that already provide a huge set of custom matchers which may suit your scenario.

Final Words

Hamcrest is a powerful tool if you use it correctly. If you want to learn more about testing head over to our unit testing – novice to savvy tutorials on youtube. There will be more tutorials on this series, so subscribe to get the notification. Thanks for reading.

Leave a Comment

Your email address will not be published. Required fields are marked *