下面的代码显示了我的问题。实际上,我尝试使用 Mockito 的 ArgumentCaptor 来验证某个具体类是否调用过一次方法。如果可能的话,我想在这里使用 ArgumentCaptor,但我开始怀疑我需要使用自定义 ArgumentMatcher。
问题在于
Mockito.verify(mocked).receive(captor.capture());
行(编辑:将此添加到下面的代码中)失败并出现 TooManyActualInitations 异常(2 而不是 1)。我想了解为什么会发生这种情况 - 是 Mockito 的实现不佳还是泛型类型擦除造成的限制?
public class FooReceiver {
public void receive(Foo foo) {
}
}
public interface Foo {
}
public class A implements Foo {
}
public class B implements Foo {
}
public class TestedClass {
private FooReceiver receiver;
public TestedClass(FooReceiver receiver) {
this.receiver = receiver;
}
public void doStuff() {
receiver.receive(new A());
receiver.receive(new B());
}
}
public class MyTest {
@Test
public void testingStuff() {
// Setup
FooReceiver mocked = Mockito.mock(FooReceiver.class);
TestedClass t = new TestedClass(mocked);
// Method under test
t.doStuff();
// Verify
ArgumentCaptor<B> captor = ArgumentCaptor.forClass(B.class);
Mockito.verify(mocked).receive(captor.capture()); // Fails here
Assert.assertTrue("What happened?", captor.getValue() instanceof B);
}
}
编辑: 对于任何感兴趣的人,我最终这样做了:
// Verify
final B[] b = new B[1];
ArgumentMatcher<B> filter = new ArgumentMatcher<B>() {
@Override
public boolean matches(Object argument) {
if(argument instanceof B) {
b[0] = (B) argument;
return true;
}
return false;
}
}
Mockito.verify(mocked).receive(Mockito.argThat(filter));
据我所知,这是一个限制/实施不佳。 当看到
org.mockito.internal.matchers.CapturingMatcher
时,有
public boolean matches(Object argument) {
return true;
}
意味着它匹配每个参数/类。
这会导致
org.mockito.internal.matchers.CapturingMatcher#getAllValues
返回一个 List<B>
,但实际上包含一个 A
和一个 B
,在运行时尝试将它们获取为 ClassCastException
时会导致 B
。
List<Object> arguments; // the invocations
// adds a new invocation
public void captureFrom(Object argument) {
// ...
this.arguments.add(argument);
// ...
}
// return the list of arguments, using raw types remove any compiler checks for validity,
// the returned List contains elements that are not of type T
public List<T> getAllValues() {
// ...
return new ArrayList<T>((List) arguments);
// ...
}
这应该可以通过更改
org.mockito.ArgumentCaptor
来解决,将其 Class<? extends T> clazz
传递到 CapturingMatcher
中,从而正确传递类型信息,实现正确的 matches
实现并消除对强制转换/原始的需要类型用法。
该方法将被调用两次,因此您需要这样做:
Mockito.verify(mocked, times(2)).receive(captor.capture());
private static class CapturingMatcherB<T> extends CapturingMatcher<T> {
public boolean matches(Object argument) {
return argument instanceof B;
}
}
CapturingMatcherB<B> captor = new CapturingMatcherB<>();
verify(mocked).receive(Mockito.argThat(captor));
Assert.assertTrue("What happened?", captor.getLastValue() instanceof B);
来源:luk2302的答案(用代码实现)
java似乎不喜欢“instanceof T”。使用匿名类而不是私有静态类也给我的覆盖带来了麻烦。
这比提问者(user545680)聪明的 ArgumentMatcher 解决方案更短、更简洁。
通用版本:(有点长,不硬编码“B”,推荐)
private static class CapturingMatcherGeneric<T> extends CapturingMatcher<T> {
private final Class<T> typeParameterClass;
public CapturingMatcherGeneric(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public boolean matches(Object argument) {
return argument.getClass().isAssignableFrom(typeParameterClass);
}
}
CapturingMatcherGeneric<B> capture = new CapturingMatcherGeneric<>(B.class);
verify(mocked).receive(Mockito.argThat(captor));
Assert.assertTrue("What happened?", captor.getLastValue() instanceof B);
使用 ArgumentMatcher/CapturesArguments 的通用版本:(只是为了展示它是如何工作的 - 它更长)
private static class CapturingMatcherGeneric2<T> implements ArgumentMatcher<T>, CapturesArguments {
private final Class<T> typeParameterClass;
private final List<Object> arguments = new ArrayList();
public CapturingMatcherGeneric2(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
@Override
public boolean matches(T argument) {
return argument.getClass().isAssignableFrom(typeParameterClass);
}
@Override
public void captureFrom(Object argument) {
this.arguments.add(argument);
}
public T getLastValue() {
return (T) this.arguments.get(this.arguments.size() - 1);
}
}