Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Mockito verify an argument has certain properties/fields?

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.

like image 858
Captain Man Avatar asked Aug 13 '15 16:08

Captain Man


People also ask

How does verify work in Mockito?

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.

Does Mockito verify use equals?

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.

What is argument matcher in Mockito?

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.


3 Answers

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.

like image 156
Niccolò Avatar answered Oct 18 '22 20:10

Niccolò


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 )));
like image 38
Captain Man Avatar answered Oct 18 '22 20:10

Captain Man


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());
like image 10
yuranos Avatar answered Oct 18 '22 21:10

yuranos