Hamcrest Custom Matchers


Welcome to this tutorial on creating custom Hamcrest matchers.

In this tutorial, we will cover the following topics.

Introduction

In a previous article, we learned how versatile Hamcrest matchers are. Listed below are some of the great benefits that Hamcrest matchers provide.

  • Improved code readability
  • Allows for code that is self-documenting
  • Makes debugging easier by way of better error messaging

Did you know that there is another powerful feature that Hamcrest has? It allows us to create our very own custom matchers to fit our testing needs. You can think of custom Hamcrest matchers as a browser plug-in; in the sense that it makes a good thing be even better.

When to Use Custom Matchers

Here are some of the reasons why you may want to create a custom Hamcrest matcher.

  • To bundle multiple assertions into one.
  • To create a sort of Domain Specific Language (DSL) within your test framework.
  • You are not able to find a built-in matcher that suits our needs.

Creating a Custom Matcher

The process of creating a custom matcher is pretty straight forward. In order to do this, we need to make use of the TypeSafeMatcher class in Hamcrest. Since this class is an abstract class, there are two methods that we are going to need to implement when extending this class; they are matchesSafely and describeTo. The former performs the actual assertion and the latter is used for providing a custom error message when a test fails.

The first thing we are going to need is to add the following Maven dependency.

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

Click here to see the latest version.

Now, let us assume that we have the following two scenarios to test.

  1. Test that an account number starts with “XYZ” and ends in “-2020”
  2. Test that a patient’s temperature above 100.4° Fahrenheit corresponds to having a fever.

For the first scenario, we will have a class called Temperature. We know that we are testing a temperature value so we need a Matcher<Double> and we can achieve this by subclassing the TypeSafeMatcher class as follows.

package io.automatenow.hamcrestmatchers.custommatchers;

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

public class Temperature extends TypeSafeMatcher<Double> {
     @Override
     public boolean matchesSafely(Double temperature) {
         return (temperature > 100.4);
     }
     
     public void describeTo(Description description) {
         description.appendText("a temperature greater than 100.4F"); 
     } 

     public static Matcher aFever() {
         return new Temperature(); 
     }
}

Notice that matchesSafely will return true if the temperature is above 100.4 and false otherwise. We then use the describeTo method to add our own error message. Lastly, we need a static method that will allow us to use our custom matcher just like any other built-in Hamcrest matcher; that is the purpose of the aFever method.

In the second scenario, we will have a class called AccountNumber as follows.

package io.automatenow.hamcrestmatchers.custommatchers;

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

public class AccountNumber extends TypeSafeMatcher<String> {
    @Override
    public boolean matchesSafely(String number) {
        return (number.startsWith("XYZ") && 
            number.endsWith("-2020")); 
    } 

    public void describeTo(Description description) { 
       description.appendText("a valid account number"); 
    }

    public static Matcher aValidAccountNumber() {
        return new AccountNumber();
    }
}

Similarly, the matchesSafely method contains our matcher logic and we will be using the aValidAccountNumber method to access our matcher.

Using a Custom Matcher

All that we need now that we have created our custom matchers is for a way to test them. We will do that by writing some tests. Our test class will statically import our two matcher methods, aFever and aValidAccountNumber. The code example is below.

package io.automatenow.hamcrestmatchers.custommatchers;
 
import org.testng.annotations.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static io.automatenow.hamcrestmatchers.custommatchers.AccountNumber.aValidAccountNumber;
import static io.automatenow.hamcrestmatchers.custommatchers.Temperature.aFever;

public class HamcrestCustomMatchers {

    @Test public void testValidAccountNumber1() {
        String accountNumber = "XYZ387457-2020";
        assertThat(accountNumber, is(aValidAccountNumber()));
    }

   @Test public void testPatientIsSick1() {
        double patientTemperature = 102.5;      
        assertThat(patientTemperature, is(aFever()));
   }
}

Notice that both of these tests will pass since they fall within our required parameters. However, the following two tests will fail.

@Test
public void testValidAccountNumber2() {
    String accountNumber = "XYZ387457-2019";
    assertThat(accountNumber, is(aValidAccountNumber()));
}

@Test
public void testPatientIsSick2() {
    double patientTemperature = 98.3;
    assertThat(patientTemperature, is(aFever()));
}

The error message that we get for test testValidAccountNumber2 is

java.lang.AssertionError: 
Expected: is a valid account number
     but: was "XYZ387457-2019" 

and the error for testPatientIsSick2 is

java.lang.AssertionError: 
Expected: is a temperature greater than 100.4 F
     but: was <98.3>

Conclusion

If you want tests that are easy to read and write and as well that provide excellent error messaging, Hamcrest custom matchers is the way to go.

Now that you have seen how easy it is to create custom matchers, it is your turn to create your own!

You can find the source code for all the examples listed in this post by visiting the AUTOMATENOW GitHub page.

References
http://hamcrest.org/

https://bit.ly/3nspZ3z

Suggested Article / Testing With Hamcrest

One thought on “Hamcrest Custom Matchers

Leave a Reply