Overview

How to write custom hamcrest matchers

Custom Matchers

Referenced classes are part of the project io.github.leoniedermeier.utils (https://github.com/LeoNiedermeier/io.github.leoniedermeier.utils/tree/master/src/main/java/io/github/leoniedermeier/utils/test/hamcrest).

The TransformingMatcher Class

The TransformingMatcher applies a Function<T,R> to the initial value and calls a Matcher<R> for the transformed value (note that the TransformingMatcher is a Matcher<T> ).

  assertThat("abcd", new TransformingMatcher<String, Integer>(String::length, "myText", Matchers.greaterThan(2)));

PropertyAccess Class

The method PropertyAccess.property(Function<T, R> transformer, String description) returns an interface with the single method Matcher<T> is(Matcher<? super R> matcher). These two methods can be combined to replace a constructor call of TransformingMatcher.

assertThat("abcd", PropertyAccess.property(String::length, "length of string").is(Matchers.greaterThan(2)));

// or with static imports
assertThat("abcd", property(String::length, "length of string").is(greaterThan(2)));

// Modell classes
class Person {
    private final List<Phone> phones = new ArrayList<>();
    public void addPhone(Phone phone) {
        phones.add(phone);
    }
    public List<Phone> getPhones() {
        return phones;
    }
}

class Phone {
    private String number;

    public Phone(String number) {
        this.number = number;
    }

    public String getNumber() {
        return number;
    }
}

// Factory method for reuseable Matchers
// with the help of PropertyAccess#property and org.hamcrest.Matchers static methods
static Matcher<Person> allPhoneNumbersStartWith(String string) {
    return property(Person::getPhones, " person phones ")
                .is(everyItem(property(Phone::getNumber, " number ").is(startsWith(string))));
}

// Test setup
Person person = new Person("Name");
person.addPhone(new Phone("0049-1234"));
person.addPhone(new Phone("0049-ABCD"));

// Test method
assertThat(person, allPhoneNumbersStartWith("0049"));

ExceptionMatchers Class

The class io.github.leoniedermeier.utils.test.hamcrest.ExceptionMatchers provides a method throwA. This method returns a matcher for a ExceptionMatchers.Executable. An Executable is a functional interface with a single none arguments method which can throw an Exception.

assertThat(() -> myMethod(arg1, arg2), throwsA(SQLException.class)));
assertThat(() -> myMethod(arg1, arg2), throwsA(SQLException.class)));

With the with method of the ExceptionMatcher is is possible to define additional matchers. This matchers apply to the thrown exception.

assertThat(() -> myMethod(arg1, arg2), throwsA(SQLException.class)).with(matcherForException) ));

Together with the property matcher feature, one can write a matcher like:

assertThat(() -> myMethod(arg1, arg2), 
      throwsA(SQLException.class)).
          with(property(SQLException::getSQLState, " sql-state ").is(equalTo("Expected-SQL-STATE"))));

It checks whether the method throws a SQLException and the getSQLState method of SQLException returns “Expected-SQL-STATE”.

References