Проверка вызовов: 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» значит, что мок не вызывали вовсе: проверьте, что в части «действие» вызывается нужный метод и что сервис получил именно этот мок.