【问题标题】:mockito verify interactions with ArgumentCaptormockito 验证与 ArgumentCaptor 的交互
【发布时间】:2013-06-16 18:25:18
【问题描述】:

要检查与方法调用中的参数属于某种类型的模拟的交互次数,可以这样做

mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(isA(FirstClass.class));

这要归功于对 isA 的调用,因为 someMethod 被调用了两次,但只有一次使用参数 FirstClass

但是,使用 ArgumentCaptor 时,这种模式似乎是不可能的,即使 Captor 是为特定参数 FirstClass 创建的

这不起作用

mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
verify(mock, times(1)).someMethod(captor.capture());

它说模拟被多次调用。

在捕获参数以供进一步检查的同时,有什么方法可以完成此验证?

【问题讨论】:

    标签: java unit-testing mocking mockito


    【解决方案1】:

    我建议使用 Mockito 的 Hamcrest 集成为它编写一个好的、干净的匹配器。这允许您将验证与传递参数的详细检查结合起来:

    import static org.mockito.hamcrest.MockitoHamcrest.argThat;
    
    verify(mock, times(1)).someMethod(argThat(personNamed("Bob")));
    
    Matcher<Person> personNamed(final String name) {
        return new TypeSafeMatcher<Person>() {
            public boolean matchesSafely(Person item) {
                return name.equals(item.getName());
            }
            public void describeTo(Description description) {
                description.appendText("a Person named " + name);
            }
        };
    }
    

    匹配器通常会导致更易读的测试和更有用的测试失败消息。它们也往往是非常可重用的,您会发现自己建立了一个为测试您的项目量身定制的库。最后,您还可以使用 JUnit 的 Assert.assertThat() 将它们用于正常的测试断言,因此您可以双重使用它们。

    【讨论】:

    • 这并不能真正回答 OP 的问题,是吗?
    • @fge:严格来说,不,它没有,但如果我们只准确地回答所提出的问题,这将是一个更无聊的地方,人们会学得更少。我认为这是对所提出的整体问题的正确解决方案,即使实际问题,如措辞所言,并没有完全要求这个。
    • 这是最优雅的解决方案,也是我实际使用的解决方案
    • 非常感谢您提出这个问题。这正是我一直在寻找的。我无法理解 ArgumentCapture 示例。不过这很完美。
    • 我强烈支持@RyanStewart 的上述断言。有时您需要解决 OP应该真正在做什么,而不是回答他们的直接问题
    【解决方案2】:

    引用文档:

    注意ArgumentCaptor不做任何类型检查,它只是 那里以避免在您的代码中进行强制转换。然而,这可能会改变(类型 检查可以添加)在未来的主要版本中。

    我不会为此使用ArgumentCaptor。此类捕获(字面意思)所有内容,尽管提供了哪个类,因为它是 .forClass 参数。

    为了实现你想要的,我建议使用 Mockito 的 Answer 接口截取参数:

    private FirstClass lastArgument;
    
    @Test
    public void captureFirstClass() throws Exception {
        doAnswer(captureLastArgument()).when(mock).someMethod(anInstanceOfFirstClass());
        mock.someMethod(new FirstClass());
        mock.someMethod(new OtherClass());
    
        verify(mock, times(1)).someMethod(anInstanceOfFirstClass());
        //write your desired matchers against lastArgument object
    }
    
    private Answer<FirstClass> captureLastArgument() {
        return new Answer<FirstClass>() {
            @Override
            public FirstClass answer(InvocationOnMock invocation) throws Throwable {
                TestClass.this.lastArgument = (FirstClass) invocation.getArguments()[0];
                return null;
            }
        };
    }
    
    private static Object anInstanceOfFirstClass(){
        return Mockito.argThat(isA(FirstClass.class));
    }
    

    【讨论】:

    • 有两种简化方法。只需使用 Mockito isA 匹配器,而不是 Hamcrest 匹配器。那么你就不需要调用argThat,当然也不应该有额外的方法来做这件事。此外,您永远不需要写times(1)。一次性验证是 Mockito 的默认设置 - 只需省略 verify 的第二个参数。
    • 谢谢,我不知道 mockito 有它自己的 isA 版本可以使用。但我仍然更喜欢使用times(1)only() 明确说明预期方法调用的数量,以提高可读性 (IMO)。
    • 这有点冗长,所以它降低了测试的可读性,但它实现了在参数末尾引用的目标。 +1
    【解决方案3】:

    您可以使用捕获器进行捕获,然后分别验证每种参数类型的调用次数。

        // given
        ArgumentCaptor<AA> captor = ArgumentCaptor.forClass(AA.class);
        CC cc = new CC();
        // when
        cut.someMethod(new AA());
        cut.someMethod(new BB());
        cut.someMethod(new BB());
        cut.someMethod(cc);
        // then
        Mockito.verify(collaborator, atLeastOnce()).someMethod(captor.capture());
        Mockito.verify(collaborator, times(1)).someMethod(isA(AA.class));
        Mockito.verify(collaborator, times(2)).someMethod(isA(BB.class));
        Mockito.verify(collaborator, times(1)).someMethod(isA(CC.class));
        assertEquals(cc, captor.getValue());
    

    显然,captor 引用的泛型类型在运行时不会影响任何内容。

    【讨论】:

    • 提醒一下:capture() 正在捕获所有参数,captor.getValue() 返回最后一个使用的参数,而不管作为参数传递给 ArgumentCaptor.forClass 的类
    • 这是最简单的解决方案。它工作正常如果一个人记住@MarlonBernardes 评论。 +1
    【解决方案4】:

    我今天也遇到了这个问题。我以为我可以简单地做类似的事情

    verify(mock).someMethod(and(isA(FirstClass.class), captor.capture()));
    

    但我无法让它工作。我最终得到了这个解决方案:

    @Test
    public void Test() throws Exception {
        final ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
    
        mock.someMethod(new FirstClass());
        mock.someMethod(new OtherClass());
    
        verify(eventBus, atLeastOnce()).post(captor.capture());
        final List<FirstClass> capturedValues = typeCheckedValues(captor.getAllValues(), FirstClass.class);
        assertThat(capturedValues.size(), is(1));
        final FirstClass capturedValue = capturedValues.get(0);
        // Do assertions on capturedValue
    }
    
    private static <T> List<T> typeCheckedValues(List<T> values, Class<T> clazz) {
        final List<T> typeCheckedValues = new ArrayList<>();
        for (final T value : values) {
            if (clazz.isInstance(value)) {
                typeCheckedValues.add(value);
            }
        }
        return typeCheckedValues;
    }
    

    注意:如果这样只需要捕获一个类,typeCheckedValues可以简化为:

    private static List<FirstClass> typeCheckedValues(List<FirstClass> values) {
        final List<FirstClass> typeCheckedValues = new ArrayList<>();
        for (final Object value : values) {
            if (value instanceof FirstClass) {
                typeCheckedValues.add((FirstClass) value);
            }
        }
        return typeCheckedValues;
    }
    

    【讨论】:

      猜你喜欢
      • 2012-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-24
      • 2013-08-13
      • 2019-11-07
      • 1970-01-01
      相关资源
      最近更新 更多