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?
Since Mockito any(Class) and anyInt family matchers perform a type check, thus they won't match null arguments. Instead use the isNull 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.
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.
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.
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With