Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Проверка вызовов: verify

Некоторые методы не возвращают ничего полезного — их работа в побочном эффекте. publish() должен сохранить пост и отправить уведомление; notifier.send() возвращает void, сравнивать через assertEquals нечего. Для таких случаев мок запоминает каждый полученный вызов, а verify проверяет, что нужный вызов был.

verify

// src/test/java/com/example/blog/PostServiceVerifyTest.java
package com.example.blog;
 
import org.junit.jupiter.api.Test;
 
import java.util.Optional;
 
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
 
class PostServiceVerifyTest {
 
    private final PostRepository repository = mock(PostRepository.class);
    private final Notifier notifier = mock(Notifier.class);
    private final PostService service = new PostService(repository, notifier);
 
    @Test
    void publishSavesPostAndNotifiesAuthor() {
        when(repository.findById(1L))
                .thenReturn(Optional.of(new Post(1L, "Привет", "anna", false)));
 
        service.publish(1L);
 
        verify(repository).save(new Post(1L, "Привет", "anna", true));
        verify(notifier).send("anna", "Ваш пост «Привет» опубликован");
    }
}

verify(notifier).send(...) читается как «проверь, что send вызвали с такими аргументами». Аргументы сравниваются через equals(); у записи это сравнение всех полей, так что первая строка заодно проверяет, что сохранён опубликованный пост. Моки созданы в полях: JUnit делает новый экземпляр класса для каждого теста, поэтому каждый тест получает свежие моки.

Сколько раз

Второй аргумент verify задаёт число вызовов:

РежимСмысл
verify(mock)Ровно один раз, то же, что times(1)
times(2)Ровно два раза
never()Ни разу
atLeastOnce(), atLeast(2), atMost(3)Не меньше / не больше
// src/test/java/com/example/blog/PostServiceVerifyTest.java
@Test
void unknownPostIsNotSaved() {
    when(repository.findById(99L)).thenReturn(Optional.empty());
 
    assertThrows(PostNotFoundException.class, () -> service.publish(99L));
 
    verify(repository, never()).save(new Post(99L, "Привет", "anna", true));
    verifyNoInteractions(notifier);
}

verifyNoInteractions(notifier) проверяет, что к уведомителю вообще не обращались — никто не должен получить письмо о несуществующем посте. verifyNoMoreInteractions(mock) после нескольких verify проверяет, что других вызовов, кроме уже проверенных, не было.

Сообщения Mockito

Если сервис отправил другой текст, Mockito покажет и ожидаемый, и фактический вызов:

Argument(s) are different! Wanted:
notifier.send(
    "anna",
    "Пост «Привет» опубликован"
);
-> at com.example.blog.VerifyFailTest.wrongArguments(VerifyFailTest.java:25)
Actual invocations have different arguments at position [1]:
notifier.send(
    "anna",
    "Ваш пост «Привет» опубликован"
);
-> at com.example.blog.PostService.publish(PostService.java:30)

position [1] — второй аргумент (счёт с нуля). Когда не совпало число вызовов:

org.mockito.exceptions.verification.TooFewActualInvocations:
postRepository.findById(1L);
Wanted 2 times:
-> at com.example.blog.VerifyFailTest.tooMany(VerifyFailTest.java:42)
But was 1 time:
-> at com.example.blog.PostService.find(PostService.java:23)

Когда проверять вызовы

Проверяйте вызовы только там, где они и есть результат метода: сохранённый пост, отправленное сообщение. Не проверяйте каждый findById — такой тест ломается после любого рефакторинга, даже если поведение не изменилось. Если результат можно проверить через возвращаемое значение, предпочитайте assertEquals.

Частая ошибка

Тест вызывает verify, но проверяемый код так и не выполнился или выполнился с другими данными:

Wanted but not invoked:
notifier.send(
    "anna",
    "Ваш пост «Привет» опубликован"
);
-> at com.example.blog.VerifyFailTest.notInvoked(VerifyFailTest.java:32)
Actually, there were zero interactions with this mock.

Здесь тест вызвал service.published() вместо service.publish(1L). «Zero interactions» значит, что мок не вызывали вовсе: проверьте, что в части «действие» вызывается нужный метод и что сервис получил именно этот мок.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.