Подтвердить что ты не робот

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

он говорит, что макет вызывался более одного раза.

Есть ли способ выполнить эту проверку при захвате аргумента для дальнейшей проверки?

4b9b3361

Ответ 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;
}