Перейти к основному содержимому

Использование Mockito ArgumentCaptor

· 4 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

В этом руководстве мы рассмотрим распространенный вариант использования Mockito ArgumentCaptor в наших модульных тестах.

Кроме того, для других вариантов использования Mockito.verify см. нашу кулинарную книгу Mockito Verify .

2. Использование ArgumentCaptor

ArgumentCaptor позволяет нам захватить аргумент, переданный методу, для его проверки. Это особенно полезно, когда мы не можем получить доступ к аргументу за пределами метода, который мы хотим протестировать.

Например, рассмотрим класс EmailService с методом отправки , который мы хотели бы протестировать:

public class EmailService {

private DeliveryPlatform platform;

public EmailService(DeliveryPlatform platform) {
this.platform = platform;
}

public void send(String to, String subject, String body, boolean html) {
Format format = Format.TEXT_ONLY;
if (html) {
format = Format.HTML;
}
Email email = new Email(to, subject, body);
email.setFormat(format);
platform.deliver(email);
}

...
}

В сервисе электронной почты . send , обратите внимание, как platform.deliver принимает новое электронное письмо в качестве аргумента. В рамках нашего теста мы хотели бы убедиться, что в поле формата нового сообщения электронной почты установлено значение Format.HTML . Для этого нам нужно захватить и проверить аргумент, который передается в platform.deliver .

Давайте посмотрим, как мы можем использовать ArgumentCaptor , чтобы помочь нам.

2.1. Настройте модульный тест

Во-первых, давайте создадим наш класс модульного теста:

@RunWith(MockitoJUnitRunner.class)
public class EmailServiceUnitTest {

@Mock
DeliveryPlatform platform;

@InjectMocks
EmailService emailService;

...
}

Мы используем аннотацию @Mock для имитации DeliveryPlatform , которая автоматически внедряется в наш EmailService с аннотацией @InjectMocks . Дополнительные сведения см. в нашей статье с аннотациями Mockito .

2.2. Добавьте поле ArgumentCaptor

Во-вторых, давайте добавим новое поле ArgumentCaptor типа Email для хранения захваченного аргумента:

@Captor
ArgumentCaptor<Email> emailCaptor;

2.3. Зафиксируйте аргумент

В-третьих, давайте используем Mockito.verify с ArgumentCaptor для захвата электронной почты :

Mockito.verify(platform).deliver(emailCaptor.capture());

Затем мы можем получить захваченное значение и сохранить его как новый объект электронной почты :

Email emailCaptorValue = emailCaptor.getValue();

2.4. Осмотрите захваченное значение

Наконец, давайте посмотрим весь тест с утверждением для проверки захваченного объекта электронной почты :

@Test
public void whenDoesSupportHtml_expectHTMLEmailFormat() {
String to = "info@foreach.com";
String subject = "Using ArgumentCaptor";
String body = "Hey, let'use ArgumentCaptor";

emailService.send(to, subject, body, true);

Mockito.verify(platform).deliver(emailCaptor.capture());
Email value = emailCaptor.getValue();
assertEquals(Format.HTML, value.getFormat());
}

3. Избегайте заглушек

Хотя мы можем использовать ArgumentCaptor с заглушками, обычно этого следует избегать. Чтобы уточнить, в Mockito это обычно означает отказ от использования ArgumentCaptor с Mockito.when . Вместо этого с заглушкой мы должны использовать ArgumentMatcher .

Давайте рассмотрим несколько причин, по которым нам следует избегать заглушек.

3.1. Снижение читабельности теста

Сначала рассмотрим простой тест:

Credentials credentials = new Credentials("foreach", "correct_password", "correct_key");
Mockito.when(platform.authenticate(Mockito.eq(credentials)))
.thenReturn(AuthenticationStatus.AUTHENTICATED);

assertTrue(emailService.authenticatedSuccessfully(credentials));

Здесь мы используем Mockito.eq(credentials) , чтобы указать, когда макет должен возвращать объект.

Далее рассмотрим тот же тест с использованием ArgumentCaptor :

Credentials credentials = new Credentials("foreach", "correct_password", "correct_key");
Mockito.when(platform.authenticate(credentialsCaptor.capture()))
.thenReturn(AuthenticationStatus.AUTHENTICATED);

assertTrue(emailService.authenticatedSuccessfully(credentials));
assertEquals(credentials, credentialsCaptor.getValue());

В отличие от первого теста, обратите внимание, как мы должны выполнить дополнительное утверждение в последней строке, чтобы сделать то же самое, что и Mockito.eq(credentials) .

Наконец, обратите внимание, что не сразу становится понятно, на что ссылается учетная запись Captor.capture() . Это связано с тем, что мы должны создать захват за пределами строки, в которой мы его используем, что снижает читабельность.

3.2. Уменьшенная локализация дефектов

Другая причина заключается в том, что если emailService.authenticatedSuccessfully не вызовет platform.authenticate , мы получим исключение:

org.mockito.exceptions.base.MockitoException: 
No argument value was captured!

Это потому, что наш заглушенный метод не захватил аргумент. Однако реальная проблема заключается не в самом тесте, а в методе, который мы тестируем.

Другими словами, это приводит нас к исключению в тесте, тогда как фактический дефект находится в тестируемом методе.

4. Вывод

В этой короткой статье мы рассмотрели общий вариант использования ArgumentCaptor . Мы также рассмотрели причины, по которым следует избегать использования ArgumentCaptor с заглушками.

Как обычно, все наши примеры кода доступны на GitHub .