Can Mockito verify an argument has certain properties/fields?

JavaUnit TestingMockingMockito

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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

Solution 3 - Java

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());

Solution 4 - Java

In case you are looking for the when.. then.. syntax, this should be a working alternative:

doReturn(valueToReturn).when(mockedFoo).doThing(argThat((Bar aBar) -> aBar.getI() == 5));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCaptain ManView Question on Stackoverflow
Solution 1 - JavaNiccolòView Answer on Stackoverflow
Solution 2 - JavaCaptain ManView Answer on Stackoverflow
Solution 3 - JavayuranosView Answer on Stackoverflow
Solution 4 - JavaYosi DahariView Answer on Stackoverflow