Mockito mock вызывает фактическую реализацию, когда не сообщается - PullRequest
0 голосов
/ 07 мая 2020

У меня есть класс, который содержит следующие три метода:

void add(Service... objs)
void add(Collection<Service> objs)
void add(Stream<Service> objs)

Как и следовало ожидать, все они поддерживают добавление нуля или более объектов, которые могут быть указаны индивидуально или как часть массива , коллекция или поток. Первые два варианта создают поток из своих аргументов и передают их третьему варианту, который фактически выполняет добавление.

При тестировании объекта, который использует этот класс, я создал макет объекта Mockito для представления экземпляра. этого класса, используя аннотацию Spring @MockBean. В отладчике я вижу, что тестируемый объект содержит фиктивный объект и что ожидаемый мной вызов (с одним аргументом типа Service) адресован макету. Поскольку метод, который следует вызвать, является первым вариантом (вариант varargs), и я знаю, что параметры varargs немного сложны, я закодировал тест, чтобы проверить, что макет вызывается с правильным параметром, следующим образом:

ArgumentCaptor<Service> captor = ArgumentCaptor.forClass(Service.class);
verify(theMock).add(captor.capture());
assertThat(captor.getAllValues()).containsExactly(expectedService);

Однако, когда я запускаю этот код, утверждение не выполняется, потому что список, возвращаемый captor.getAllValues ​​(), содержит не службу, а поток: сообщение об ошибке говорит:

java.lang.AssertionError: 
Expecting:
  <[java.util.stream.ReferencePipeline$Head@2cfe272f]>
to contain exactly (and in same order):
  <[com.xxx.data.Service@37c5]>
but some elements were not found:
  <[com.xxx.data.Service@37c5]>
and others were not expected:
  <[java.util.stream.ReferencePipeline$Head@2cfe272f]>

Когда я запустите код в отладчике, я вижу, что вызов от тестируемого объекта к add(Service...) вызывает реальную реализацию; это вызывает add(Stream<Service>), и это тот вызов, который перехватывается имитатором. Это объясняет, почему я вижу сбой, но я не понимаю, почему макет не может перехватить исходный вызов, или что я могу сделать, чтобы заставить его это сделать.

Ответы [ 3 ]

0 голосов
/ 08 мая 2020

Я нашел способ решения проблемы, но проблема все еще существует, и я думаю, что это, вероятно, ошибка Mockito (обозначенная как https://github.com/mockito/mockito/issues/1929).

Обходной путь - добавить этот метод в мой тестовый класс. Я добавил метод generi c, потому что это не просто вызов метода add (), который вызывает проблему, но также и вызов аналогичного перегруженного метода remove (), который принимает аргументы String.

    private <T, V> void verifyCall(T mock, BiConsumer<T, V> call, 
                                     V expectedArg, Class<V> type)
    {
        ArgumentCaptor<V> captor = ArgumentCaptor.forClass(type);
        call.accept(verify(mock), captor.capture());
        List<?> values = captor.getAllValues();
        try {
            assertThat(values.get(0)).isEqualTo(expectedArg);
        } catch (AssertionFailedError ex) {
            assertThat((Stream<V>) values.get(0)).containsExactly(expectedArg);
        }
    }

Это должно сработать независимо от того, был ли перехваченный имитацией вызов к варианту метода varargs (как и должно быть) - в этом случае утверждение в теле блока try не вызовет исключения - или к Вариант потока (как сейчас) - в этом случае утверждение в теле вызовет исключение и будет выполнено утверждение в блоке catch.

Затем, когда я хочу убедиться, что мой макет add ( ) был вызван с ожидаемым объектом Service, я делаю это с помощью:

verifyCall(theMock, Datastore::add, expectedService, Service.class);

И аналогично для метода remove ():

verifyCall(theMock, Datastore::remove, expectedDeletedKey, String.class);

Очень приятно, когда я наконец получил это работает, тест не удался, потому что я допустил ошибку в тестируемом методе. Что сделало все это стоящим.

0 голосов
/ 11 мая 2020

Ооо. Я не заметил, что два метода varargs были объявлены окончательными. Удалено это, и все работает как положено.

0 голосов
/ 07 мая 2020

Обновите свой ArgumentCaptor, чтобы он принимал Service[]

ArgumentCaptor<Service[]> serviceCaptor = ArgumentCaptor.forClass(Service[].class);

и утверждал

Service[] actualServices = serviceCaptor.getAllValues();
assertEquals(actualServices.length, 1);
assertEquals(actualServices[0], service);

И лучше всего использовать ErrorCollector в Junit для утверждения более одного

assert и SoftAssect в Testng и вызова после вашего утверждения softAssert.assertAll ()

...