Search code examples
javamockitomatcherhamcrest

Custom matchers with different object


Hi I am writing a custom matcher to validate two different object, and my code is:

public class DetailsMatcher extends ArgumentMatcher <Details> {

    private final Details expected;

    private DetailsMatcher(Details expected) {
        this.expected = expected;
    }

    @Override
    public boolean matches(Object actual) {
        return ((Request) actual).getId().equals(expected.getId());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText(expected == null ? null : expected.toString());
    }

    public static Details valueObjectEq(Details expected) {
        return argThat(new DetailsMatcher(expected));
    }
}

And I want to use it in my test as:

assertThat(requestModel, DetailsMatcher.valueObjectEq(response));

But these are two different object so that it doesn't work. I don't want to change my objects just for testing purpose, so do we have some api like assertThat, which allows passing different object to simply relies on the output of matches, rather than forcing the actual and expected of same type?


Solution

  • Be careful to keep Mockito matchers and Hamcrest matchers separate. A Hamcrest or Hamcrest-style matcher is an object instance, like your new DetailsMatcher(expected) above. With the call to argThat, your valueObjectEq appears to return a Details object, but that's not true: It will return null and Mockito will save an object on an internal stack. Consequently, you should never call argThat outside of a call to when or verify.

    Instead, change your DetailsMatcher to be of type ArgumentMatcher<Request> to indicate that your Matcher can work on any Request, not just Details, and make the constructor public so you can call it like this:

    assertThat(requestModel, new DetailsMatcher(orderResponse));
    // or with a static helper method you write:
    assertThat(requestModel, matchesValueObject(orderResponse));
    

    A few other notes:

    • Matcher.matches should never throw an exception, but it will do so here if you pass in a non-Request. Check with instanceof and return false if the argument isn't a Matcher.
    • Mockito 2.0 makes ArgumentMatcher its own class, rather than extending org.hamcrest.Matcher. If you want to keep using the Matcher for its Hamcrest properties, you may need to have it extend both, or just make it a Hamcrest matcher and adjust valueObjectEq to call MockitoHamcrest.argThat instead.
    • Though the error message won't be as clear, you can always call the method manually:

      assertTrue(new DetailsMatcher(orderReponse).matches(requestModel));
      

    See also: