Say I am mocking this class Foo
class Foo {
public void doThing(Bar bar) {
// ...
}
}
and this is Bar
class Bar {
private int i;
public int getI() { return i; }
public void setI(int i) { this.i = i; }
}
I know I can use Mockito's verify functionality to see if Foo#doThing(Bar)
was called on the mock with a specific instance of Bar
or any Bar
with Mockito.any(Bar.class)
, but is there some way to ensure it was called by any Bar
but with a specific value for i
or Bar#getI()
?
What I know is possible:
Foo mockedFoo = mock(Foo.class);
Bar someBar = mock(Bar.class);
...
verify(mockedFoo).doThing(someBar);
verify(mockedFoo).doThing(any(Bar.class);
What I want to know is if there is a way to verify that a Bar
with particular things true about it was passed as an argument.
Mockito verify() method can be used to test number of method invocations too. We can test exact number of times, at least once, at least, at most number of invocation times for a mocked method. We can use verifyNoMoreInteractions() after all the verify() method calls to make sure everything is verified.
Internally Mockito uses Point class's equals() method to compare object that has been passed to the method as an argument with object configured as expected in verify() method. If equals() is not overridden then java. lang.
Argument matchers are mainly used for performing flexible verification and stubbing in Mockito. It extends ArgumentMatchers class to access all the matcher functions. Mockito uses equal() as a legacy method for verification and matching of argument values.
In Mockito 2.1.0 and up with Java 8 you can pass the lambda to argThat out of the box so that one does not need a custom argument matchers. For the example in the OP would be:
verify(mockedFoo).doThing(argThat((Bar aBar) -> aBar.getI() == 5));
This is because as of Mockito 2.1.0, ArgumentMatcher
is a functional interface.
If you are using Mockito 2.1.0 or above and Java 8 or above, see this answer instead, it's much simpler now.
I found the answer while writing the question.
Yes, you can. Instead of using any(Bar.class)
you'll need to implement your own instance of ArgumentMatcher<T>
and use Mockito#argThat(Matcher)
, for example, say we want to check that i
is 5...
// in the test (could also be outside)
private static final class BarIs5 extends ArgumentMatcher<Bar> {
@Override
public boolean matches(Object argument) {
return ((Bar) argument).getI() == 5;
}
}
Then verify like so: verify(mockedFoo).doThing(argThat(new BarIs5()));
Kick it up a notch by adding constructor parameters!
private static final class BarIsWhat extends ArgumentMatcher<Bar> {
private final int i;
public BarIsWhat(int i) {
this.i = i
}
@Override
public boolean matches(Object argument) {
return ((Bar) argument).getI() == i;
}
}
Then verify like so: verify(mockedFoo).doThing(argThat(new BarIsWhat(5)));
Update: This popped in my queue because of a badge and saw some room for improvement.
I have tried this and it works. You can sort of use a lambda expression which is a lot cleaner (if you don't mind unchecked cast warnings at least).
The only issue is that argThat
accepts a Hamcrest Matcher
which is not a @FunctionalInterface
. Luckily, Mockito's ArgumentMatcher
is an abstract class that extends it and only has a single abstract method.
In your test (or some common location) make a method like below
private static <T> ArgumentMatcher<T> matches(Predicate<T> predicate) {
return new ArgumentMatcher<T>() {
@SuppressWarnings("unchecked")
@Override
public boolean matches(Object argument) {
return predicate.test((T) argument);
}
};
}
Now, in your test you can do this to use a lambda expression:
verify(mockedFoo).doThing(argThat(matches( (Bar arg) -> arg.getI() == 5 )));
If using Mockito 2+ is not an option, you can also use good old ArgumentCaptor
. It'll be a bit more verbose though:
ArgumentCaptor<Long> siteIdCaptor = ArgumentCaptor.forClass(Long.class);
verify(repository).findBySiteId(siteIdCaptor.capture());
assertEquals(15, siteIdCaptor.getValue().longValue());
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