Как правильно мокать (mock) void методы в Mockito?
Всем привет! Я начинающий Java-разработчик, пишу unit-тесты с использованием Mockito. Столкнулся с проблемой: не могу понять, как правильно замокать метод, который ничего не возвращает (void).
Вот простой пример. Есть класс UserService, который отправляет email через EmailService:
public class EmailService {
public void sendEmail(String to, String subject, String body) {
// реальная отправка email
System.out.println("Email sent to " + to);
}
}
public class UserService {
private final EmailService emailService;
public UserService(EmailService emailService) {
this.emailService = emailService;
}
public void registerUser(String email) {
// какая-то логика регистрации
emailService.sendEmail(email, "Welcome", "Hello!");
}
}
Я пишу тест для registerUser и хочу проверить, что sendEmail был вызван. Но не понимаю, как замокать void-метод. Когда я пытаюсь использовать when(), компилятор ругается:
@Test
public void testRegisterUser() {
EmailService mockEmail = mock(EmailService.class);
UserService userService = new UserService(mockEmail);
// Это не работает!
when(mockEmail.sendEmail(anyString(), anyString(), anyString())).thenReturn(...);
userService.registerUser("test@test.com");
verify(mockEmail).sendEmail("test@test.com", "Welcome", "Hello!");
}
Ошибка: when() требует возвращаемого значения, а void-метод ничего не возвращает. Как правильно замокать такой метод? И как проверить, что он был вызван с правильными аргументами?
Заранее спасибо за помощь!
<<Основной подход: doNothing() и verify()
В Mockito для void-методов используется другой синтаксис. Вместо when() применяется doNothing() (или doThrow() для исключений), а для проверки вызова — verify(). Вот правильное решение для вашего случая:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
public class UserServiceTest {
@Test
public void testRegisterUser() {
// 1. Создаём мок
EmailService mockEmail = mock(EmailService.class);
UserService userService = new UserService(mockEmail);
// 2. Настраиваем поведение void-метода (опционально)
doNothing().when(mockEmail).sendEmail(anyString(), anyString(), anyString());
// 3. Вызываем тестируемый метод
userService.registerUser("test@test.com");
// 4. Проверяем, что метод был вызван с правильными аргументами
verify(mockEmail).sendEmail("test@test.com", "Welcome", "Hello!");
}
}
Ключевые моменты:
- doNothing() — говорит Mockito "ничего не делай при вызове этого void-метода". Это поведение по умолчанию для моков, поэтому его можно опустить, если не нужно генерировать исключение.
- verify() — проверяет, что метод был вызван хотя бы один раз с указанными аргументами. Можно уточнить количество вызовов:
verify(mock, times(2)),verify(mock, never())и т.д.
Дополнительные возможности:
1. Имитация исключения (doThrow)
// Метод выбросит исключение при вызове
doThrow(new RuntimeException("Ошибка отправки"))
.when(mockEmail).sendEmail(anyString(), anyString(), anyString());
// Проверяем, что исключение обрабатывается
assertThrows(RuntimeException.class, () -> {
userService.registerUser("test@test.com");
});
2. Проверка порядка вызовов (InOrder)
InOrder inOrder = inOrder(mockEmail);
inOrder.verify(mockEmail).sendEmail("a@a.com", "Subj1", "Body1");
inOrder.verify(mockEmail).sendEmail("b@b.com", "Subj2", "Body2");
3. Проверка аргументов с ArgumentCaptor
Если нужно проверить сложные аргументы:
ArgumentCaptor<String> toCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> subjectCaptor = ArgumentCaptor.forClass(String.class);
verify(mockEmail).sendEmail(toCaptor.capture(), subjectCaptor.capture(), anyString());
assertEquals("test@test.com", toCaptor.getValue());
assertEquals("Welcome", subjectCaptor.getValue());
Важные замечания:
- Для void-методов нельзя использовать
when().thenReturn()— это синтаксическая ошибка. - Если мок создан через
@Mockаннотацию, синтаксис остаётся тем же. - Для методов, возвращающих
void, можно также использоватьdoAnswer(), если нужно выполнить какое-то действие при вызове.
Надеюсь, теперь стало понятнее! Главное правило: void-методы мокаются через doNothing/doThrow/doAnswer, а проверяются через verify.
Альтернативный подход: Использование doAnswer() для кастомного поведения
Иногда нужно не просто заглушить void-метод, а выполнить какую-то логику при его вызове. Например, изменить состояние объекта или записать аргументы. В таких случаях помогает doAnswer():
// Создаём список для отслеживания вызовов
List<String> sentEmails = new ArrayList<>();
doAnswer(invocation -> {
String to = invocation.getArgument(0);
String subject = invocation.getArgument(1);
String body = invocation.getArgument(2);
// Сохраняем информацию о вызове
sentEmails.add(to + " - " + subject);
// Возвращаем null, так как метод void
return null;
}).when(mockEmail).sendEmail(anyString(), anyString(), anyString());
// Вызываем тестируемый метод
userService.registerUser("test@test.com");
// Проверяем, что email был "отправлен"
assertEquals(1, sentEmails.size());
assertTrue(sentEmails.get(0).contains("test@test.com"));
Когда это полезно:
- Нужно проверить не только факт вызова, но и порядок обработки аргументов.
- Метод void изменяет внешнее состояние (например, базу данных), и вы хотите это смоделировать.
- Хотите добавить логирование или подсчёт вызовов без создания реального сервиса.
Обратите внимание: doAnswer() работает как с void, так и с возвращающими значение методами. Для void-методов лямбда должна возвращать null.