31

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

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

这将通过调用isAsincesomeMethod被调用两次,但只有一次使用参数 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());

它说模拟被多次调用。

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

4

4 回答 4

32

我建议使用 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(),因此您可以双重使用它们。

于 2013-06-20T03:58:00.547 回答
8

引用文档:

请注意,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));
}
于 2013-06-20T02:50:19.230 回答
4

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

    // 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 引用的泛型类型在运行时不会影响任何内容。

于 2013-06-20T03:35:28.337 回答
0

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

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;
}
于 2017-01-05T10:40:39.277 回答