使用 Mockito 的 ArgumentCaptor 类来匹配子类

发布于 2024-10-25 20:06:29 字数 1576 浏览 2 评论 0原文

下面的代码显示了我的问题。实际上,我尝试使用 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));

The below code shows my problem. Effectively, I am trying to use Mockito's ArgumentCaptor to verify that a method was called once with a certain concrete class. I would like to use ArgumentCaptor here if possible, but I am beginning to suspect I need to use a custom ArgumentMatcher instead.

The problem is that the line Mockito.verify(mocked).receive(captor.capture()); (Edit: Added this to the code below) fails with a TooManyActualInvocations exception (2 instead of 1). I would like to understand why this is happening - is it poor implementation of Mockito or a limitation caused by type erasure of generics?

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

EDIT:
For anyone interested, I ended up doing this:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

早乙女 2024-11-01 20:06:29

据我所知,这是一个限制/实施不佳。
当查看 org.mockito.internal.matchers.CapturingMatcher 时,它

public boolean matches(Object argument) {
    return true;
}

意味着它匹配每个参数/类。

这会导致 org.mockito.internal.matchers.CapturingMatcher#getAllValues 返回一个 List,但实际上包含一个 A 和一个 B 在运行时尝试将它们获取为 B 时会导致 ClassCastException

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 clazz 到 CapturingMatcher 中,从而正确传递类型信息,从而实现正确的 matches 实现并消除对强制转换/原始类型使用的需要。

As far as I can tell this is a limitation / poor implementation.
When looking at org.mockito.internal.matchers.CapturingMatcher there is

public boolean matches(Object argument) {
    return true;
}

meaning it matches every argument / class.

This results in org.mockito.internal.matchers.CapturingMatcher#getAllValues returning a List<B> but actually containing one A and one B resulting in a ClassCastException during runtime when trying to get them as 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);
    // ... 
}

This should be solvable by changing org.mockito.ArgumentCaptor in a way that it passes its Class<? extends T> clazz into the CapturingMatcher and therefore passing the type information along properly, enabling a proper matches implementation and removing the need for the cast / raw type usage.

无法回应 2024-11-01 20:06:29

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

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

Mockito JavaDoc

You can also use Mockito.isA to verify that the argument is of a specific class:

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

Mockito JavaDoc

怀里藏娇 2024-11-01 20:06:29

该方法将被调用两次,因此您需要执行以下操作:

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

The method will be called twice so you need to do this:

Mockito.verify(mocked, times(2)).receive(captor.capture());
没︽人懂的悲伤 2024-11-01 20:06:29
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);
    }
}
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);

Source: luk2302's answer (implemented it in code)

java didn't seem to like "instanceof T". Using an anonymous class instead of a private-static-class also gave me troubles with the override.

This is a bit shorter and cleaner-looking than the asker's (user545680) clever ArgumentMatcher solution.

Generic version: (bit longer, does not hardcode "B", recommended)

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

Generic version using ArgumentMatcher/CapturesArguments: (just to show how it works - it is longer)

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文