Hamcrest Custom Matcher

Here`s how to implement a Custom Matcher in Hamcrest.

Hamcrest provides various Matchers such as containsString(), closeTo(), etc. However, there are test cases that are difficult to implement with the matchers provided by default. Hamcrest provides the following abstract classes, which can be implemented to create custom matchers.

  • TypeSafeMatcher
  • TypeSafeDiagnosingMatcher

Set project dependencies

If your project uses Gradle, add java-hamcrest as below in java-hamcrest.

dependencies {
    testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0'
}

Custom Matcher (TypeSafeMatcher)

You can create a Custom Matcher by implementing TypeSafeMatcher.

If you inherit TypeSafeMatcher, you need to override the following two methods.

public class IsInteger extends TypeSafeMatcher<String> {
  @Override
  protected boolean matchesSafely(String item) {
      // return true if it matches
  }

  @Override
  public void describeTo(Description description) {
      // description for failure message
  }
}
  • matchesSafely : Returns a boolean indicating whether the item passed as an argument matches any condition. If true is returned, the test passes
  • describeTo : Contents displayed when the test fails

Matcher to test if a string is an Integer

The IsInteger class below is a Matcher that checks if a string is an Integer.

In matchesSafely(), simply Integer.parseInt() checks whether it is an Integer or not. In describeTo(), we entered a message that the string must consist of Integers.

public class IsInteger extends TypeSafeMatcher<String> {
    @Override
    protected boolean matchesSafely(String str) {
        try {
            Integer.parseInt(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("A String of Integer");
    }

    public static Matcher<String> isInteger() {
        return new IsInteger();
    }
}

The Matcher implemented above can be used with assertThat like this: When passing Matcher as the second argument, create a Matcher object with new IsInteger() and pass it.

@Test
public void testCustomMatcher_isInteger1() {
    String num = "1234";

    assertThat(num, new IsInteger());
}

Because the readability of creating and passing an object is not good, the IsInteger class is added as a static method that creates and returns a Custom Matcher object as follows.

public static Matcher<String> isInteger() {
    return new IsInteger();
}

Now, with this method, you can simply pass a Custom Matcher to assertThat() as shown below.

@Test
public void testCustomMatcher_isInteger2() {
    String num = "1234";

    assertThat(num, isInteger());
}

Failure message

In the test below, 1234.56 will fail because it is not an Integer.

@Test
public void testCustomMatcher_isInteger3() {
    String notNum = "1234.56";

    assertThat(notNum, isInteger());
}

If the test fails, the following failure message is output. The message output after Expected: will be the string entered in describeTo().

Expected: A String of Integer
     but: was "1234.56"

Use with other Matchers

Custom Matcher can be used with default matchers such as is() and not().

@Test
public void testCustomMatcher_isInteger4() {
    String notNum = "1234.56";

    assertThat(notNum, not(isInteger()));
}

Custom Matcher (TypeSafeDiagnosingMatcher)

This time we will create a Custom Matcher by implementing the TypeSafeDiagnosingMatcher class. TypeSafeDiagnosingMatcher can set more detailed failure messages than TypeSafeMatcher.

Matcher to test if Integer is even

This time we implement a Matcher called IsEven that checks if an Integer is even.

public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {
    @Override
    protected boolean matchesSafely(Integer integer, Description description) {
        description.appendText("was ").appendValue(integer)
                .appendText(", which is an Odd number");
        return integer % 2 == 0;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("An Even Number");
    }

    public static Matcher<Integer> isEven() {
        return new IsEven();
    }
}

In the case of TypeSafeDiagnosingMatcher, you can implement the following two methods. TypeSafeMatcher does not pass a Description as an argument to matchesSafely(), but does pass it here.

  • matchesSafely(Integer integer, Description description)
  • describeTo(Description description)

If you look at the implementation of matchesSafely(), put a message in the description. This message is only output when the test fails.

@Override
protected boolean matchesSafely(Integer integer, Description description) {
    description.appendText("was ").appendValue(integer)
            .appendText(", which is an Odd number");
    return integer % 2 == 0;
}

describeTo() can be implemented like TypeSafeMatcher.

failure message

The test below will fail because 13 is an odd number.

@Test
public void testCustomMatcher_isEven2() {
    Integer num2 = 13;

    assertThat(num2, isEven());
}

When I test it, the failure message is output like this: You can see that the message set in matchesSafely() is output like was <13>, which is an Odd number.

Expected: An Even Number
     but: was <13>, which is an Odd number
codechachaCopyright ©2019 codechacha