Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a custom ArgumentMatcher that accepts arguments from other matchers?

Tags:

java

mockito

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?

like image 989
Charles Spencer Avatar asked Sep 08 '16 19:09

Charles Spencer


People also ask

What can I use instead of Org Mockito matchers?

Since Mockito any(Class) and anyInt family matchers perform a type check, thus they won't match null arguments. Instead use the isNull matcher.

What is an argument matcher?

Argument matchers are placeholders use to specify what values can be used in a function. They can be used with stubs and verification. Check equality. (TODO) Checking if an argument is equal using eq , refEq , isNull , and more.

Can the Mockito Matcher methods be used as return values?

Mockito requires that we provide all arguments either by matchers or exact values. There are two more points to note when we use matchers: We can't use them as a return value; we require an exact value when stubbing calls. We can't use argument matchers outside of verification or stubbing.

What is argThat?

The argThat argument matcher in Mockito lets you create advanced argument matchers that run a function on passed arguments, and checks if the function returns true . If you have a complicated class that can't be easily checked using . equals() , a custom matcher can be a useful tool.


1 Answers

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.)

like image 137
Jeff Bowman Avatar answered Sep 28 '22 02:09

Jeff Bowman