Mockito 可以验证一个参数是否具有某些属性/字段吗?

说我在嘲笑这门课 Foo

class Foo {
public void doThing(Bar bar) {
// ...
}
}

这是 Bar

class Bar {
private int i;
public int getI() { return i; }
public void setI(int i) { this.i = i; }
}

我知道我可以使用 Mockito 的验证功能来查看是否使用特定的 Bar实例或 Mockito.any(Bar.class)但是有没有什么方法可以确保它被任何 ABC1调用,但是具有 ABC5或 Bar#getI()的特定值?的任何 Bar对 mock 调用了 Foo#doThing(Bar)

我所知道的是可能的:

Foo mockedFoo = mock(Foo.class);
Bar someBar = mock(Bar.class);
...
verify(mockedFoo).doThing(someBar);
verify(mockedFoo).doThing(any(Bar.class);

我想知道的是,是否有一种方法可以验证 Bar是否作为参数传递,该 Bar的特定条件为真。

67975 次浏览

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

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

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