Mockito проверяет взаимодействие с ArgumentCaptor
Чтобы проверить количество взаимодействий с макетом, где параметр в вызове метода имеет определенный тип, можно сделать
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());
он говорит, что макет вызывался более одного раза.
Есть ли способ выполнить эту проверку при захвате аргумента для дальнейшей проверки?
Ответы
Ответ 1
Я рекомендую использовать интеграцию Mockito Hamcrest для написания хорошего, чистого совпадения. Это позволяет комбинировать проверку с подробной проверкой переданного аргумента:
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()
, поэтому вы получаете возможность использовать их дважды.
Ответ 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));
}
Ответ 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());
По-видимому, общий тип ссылки на улавливатель не влияет ни на что во время выполнения.
Ответ 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;
}