9

I'm currently writing unit tests on a code base that uses a lot of ActionEvent's internally, and since ActionEvent doesn't override equals(), I'm creating a custom ArgumentMatcher to match ActionEvent's.

My ArgumentMatcher currently looks like this:

public class ActionEventMatcher extends ArgumentMatcher<ActionEvent> {
    private Object source;
    private int id;
    private String actionCommand;

    public ActionEventMatcher(Object source, int id, String actionCommand) {
        this.source = source;
        this.id = id;
        this.actionCommand = actionCommand;
    }

    @Override
    public boolean matches(Object argument) {
        if (!(argument instanceof ActionEvent))
            return false;
        ActionEvent e = (ActionEvent)argument;

        return source.equals(e.getSource()) &&
            id == e.getId() && actionCommand.equals(e.getActionCommand());
    }
}

I would like to know if it possible to change my ArgumentMatcher so that it accepts arguments that were created from other matchers. For instance, if I have a method called actionEvent() that returns the matcher, I would like to be able to do the following:

verify(mock).fireEvent(argThat(actionEvent(same(source), anyInt(), eq("actionCommand"))));

Is there a way to have a custom ArgumentMatcher that accepts arguments from other matchers this way?

Charles Spencer
  • 626
  • 1
  • 6
  • 17

1 Answers1

8

You'll be able to do that for Hamcrest or Hamcrest-style matchers, but not for Mockito matchers you get from the static methods on org.mockito.Matchers.

In short, methods like same, anyInt, and eq in Mockito are all designed to fit into method calls in when and verify, so they work counterintuitively through side effects. This makes it really hard to consume them and work with them outside of Mockito internals. By contrast, if you limit yourself to using either Matcher (Hamcrest) or ArgumentMatcher (Mockito) instances, you can manipulate those to your heart's content, and with Hamcrest you'll already have a large library of matchers to start with. See my other Q&A here for context.

In short, your matcher would probably look like this.

public class ActionEventMatcher extends ArgumentMatcher<ActionEvent> {
    /* fields here */

    public ActionEventMatcher(
            Matcher<Object> sourceMatcher,
            Matcher<Integer> idMatcher,
            Matcher<String> actionCommandMatcher) { /* save fields here */ }

    @Override
    public boolean matches(Object argument) {
        if (!(argument instanceof ActionEvent))
            return false;
        ActionEvent e = (ActionEvent)argument;

        return this.sourceMatcher.matches(e.getSource())
            && this.idMatcher.matches(e.getId())
            && this.actionCommandMatcher.matches(e.getActionCommand());
    }
}

As a bonus, you can use describeMismatch in Hamcrest 1.3+ to summarize mismatched fields, which might make it easier to determine which aspects of an ActionEvent are failing. (This won't help for verify calls if you don't use an ArgumentCaptor, though, because Mockito treats a non-matching call as "missing A but received B", not "received B but it failed matcher A for these reasons". You'd have to capture the event and use assertEquals to benefit from mismatch descriptions.)

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Thanks for your answer! It looks like Hamcrest is the only feasible option, so I'll take a look at the Hamcrest matchers and see if that can work for me. I'm a bit afraid that the tests could get confusing if they mix Hamcrest and Mockito matchers. I also saw that Mockito 2 is no longer dependent on Hamcrest. Are the makers of Mockito trying to distance their library from Hamcrest? – Charles Spencer Sep 08 '16 at 21:02
  • @CharlesSpencer In short, yes; it sounds like it was a source of version incompatibilities among other things. Mockito 2 will still have access to Hamcrest adapters through `MockitoHamcrest`, though, you just won't be forced into any particular version of Hamcrest solely by depending on Mockito 2 (in contrast to Mockito 1.x). – Jeff Bowman Sep 08 '16 at 21:06