使用 Mockito 的 ArgumentCaptor 类来匹配子类

问题描述 投票:0回答:4

下面的代码显示了我的问题。实际上,我尝试使用 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));
java polymorphism mockito
4个回答
7
投票

据我所知,这是一个限制/实施不佳。 当看到

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
实现并消除对强制转换/原始的需要类型用法。


6
投票

您还可以使用 Mockito.isA 来验证参数是否属于特定类:

verify(mock).init(isA(ExpectedClass.class));

Mockito JavaDoc


0
投票

该方法将被调用两次,因此您需要这样做:

Mockito.verify(mocked, times(2)).receive(captor.capture());

0
投票
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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.