Creating custom Hamcrest matchers

In this recipe, we will create a custom Hamcrest matcher. Please refer to the previous recipe in terms of the presented assertions in the test because in the current recipe, we will combine them in our custom matchers.

Getting ready

For this recipe, our system under test will be a NewPersonGenerator class that will call an external service, NewIdentityCreator, to generate a new identity for the current person, as shown in the following code:

public class NewPersonGenerator {

    private final NewIdentityCreator newIdentityCreator;

    public NewPersonGenerator(NewIdentityCreator newIdentityCreator) {
        this.newIdentityCreator = newIdentityCreator;
    }

    public Person generateNewIdentity(Person person) {
        String newName = newIdentityCreator.createNewName(person);
        int newAge = newIdentityCreator.createNewAge(person);
        List<Person> newSiblings = newIdentityCreator.createNewSiblings(person);
        return new Person(newName, newAge, newSiblings);
    }
}

How to do it...

If you want to create a custom Hamcrest matcher, you have to attach your Hamcrest dependencies (if necessary). Depending on your needs, you can extend either of the classes (BaseMatcher, TypeSafteMatcher, TypeSafeDiagnosingMatcher, or DiagnosingMatcher).

The following snippet shows the test of our system using JUnit. It calls static methods that return custom Hamcrest matchers (please refer to Chapter 1, Getting Started with Mockito, and the previous recipe for information on how to configure your test for TestNG):

@SuppressWarnings("unchecked")
@RunWith(MockitoJUnitRunner.class)
public class NewPersonGeneratorTest {

    @Mock NewIdentityCreator newIdentityCreator;

    @InjectMocks NewPersonGenerator systemUnderTest;

    @Test
    public void should_return_person_with_new_identity() {
        // given
        Person person = new Person("Robert", 25, asList(new Person("John"), new Person("Maria")));
        given(newIdentityCreator.createNewName(person)).willReturn("Andrew");
        given(newIdentityCreator.createNewAge(person)).willReturn(45);
        given(newIdentityCreator.createNewSiblings(person)).willReturn(asList(new Person("Amy", 20), new Person("Alejandro Gonzales", 25)));

        // when
        Person newPerson = systemUnderTest.generateNewIdentity(person);

        // then
        assertThat(newPerson, allOf(is(not(equalTo(person))),
                                    hasNameEqualTo("Andrew"),
                                    hasAgeGreaterThan(25),
                                    containsSiblings(new Person("Amy", 20), new Person("Alejandro Gonzales", 25))));
    }
  
}

The class that contains the static methods that create Hamcrest matchers is shown in the following code:

public class PersonMatchers {

  public static Matcher hasNameEqualTo(final String name) {
    return new BaseMatcher() {
      @Override
      public boolean matches(Object item) {
        if (!(item instanceof Person)) {
          return false;
        }
        Person person = (Person) item;
        return bothNamesAreNull(person) || bothNamesMatch(person);
      }

      private boolean bothNamesMatch(Person person) {
        return (name != null && name.equals(person.getName()));
      }

      private boolean bothNamesAreNull(Person person) {
        return (name == null && person.getName() == null);
      }

      @Override
      public void describeTo(Description description) {
        description.appendText("Name should be equal to ").appendValue(name);
      }
    };
  }

  public static Matcher<Person> hasAgeGreaterThan(final int age) {
    return new TypeSafeMatcher<Person>() {
      @Override
      protected boolean matchesSafely(Person person) {
        return person.getAge() > age;
      }

      @Override
      public void describeTo(Description description) {
        description.appendText("Age should be greater than ").appendValue(age);
      }
    };
  }

  public static Matcher<Person> containsSiblings(final Person... siblings) {
    return new TypeSafeDiagnosingMatcher<Person>() {
      @Override
      public void describeTo(Description description) {
        description.appendText("Person should have siblings ").appendValue(siblings);
      }

      @Override
      protected boolean matchesSafely(Person person, Description mismatchDescription) {
        if (!person.getSiblings().containsAll(Arrays.asList(siblings))) {
          mismatchDescription.appendText("The person has size of siblings equal to ")
              .appendValue(person.getSiblings().size())
              .appendText(" and the person has siblings ")
              .appendValue(person.getSiblings());
          return false;
        }
        return true;
      }
    };
  }
}

There are three main ways of creating custom Hamcrest matchers:

  • Extend the BaseMatcher class as follows:
    1. Verify the types and casting (check the hasNameEqualTo(...) method from the preceding example).
    2. Provide the matching logic and the description that will be appended to the core Hamcrest assertion error message.
  • Extend the TypeSafeMatcher class as follows:
    1. You don't have to take care of the casting (check the hasAgeGreaterThan(...) method from the previous example).
    2. Provide the matching logic and the description that will be appended to the core Hamcrest assertion error message.
  • Extend the TypeSafeDiagnosingMatcher or DiagnosingMatcher class as follows:
    1. Provide the matching logic.
    2. What differs from TypeSafeMatcher is that you have access to the Description object that you can already manipulate at this point (check the containsSiblings(...) method from the previous example).
    3. Provide the description that will be appended to the core Hamcrest assertion error.

How it works...

To check how Hamcrest works internally, please check the previous recipe.

Note

Remember, before you start writing your custom matcher, do not implement the Matcher interface. Instead, always extend the abstract BaseMatcher class or another class that has already implemented it.

There's more...

Hamcrest allows you to create a class that combines all of your matchers in one place. If you are interested in this feature, please refer to the documentation at https://code.google.com/p/hamcrest/wiki/Tutorial#Sugar_generation.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.77.153