- Home »

Mockito Mock для void методов — пример использования
Если ты хотя бы раз занимался серьёзной Java-разработкой, то наверняка сталкивался с ситуацией, когда нужно покрыть тестами void методы. Казалось бы, что там тестировать? Метод ничего не возвращает, но побочные эффекты могут быть самыми разными — от записи в базу данных до отправки уведомлений. Вот тут-то и приходит на помощь Mockito с его мощными возможностями для работы с void методами. Эта статья поможет тебе разобраться с тонкостями мокирования таких методов, избежать типичных ошибок и построить надёжные тесты для твоих серверных приложений.
Как работает мокирование void методов
Mockito обрабатывает void методы несколько иначе, чем обычные методы с возвращаемыми значениями. Основная разница в том, что вместо классического when().thenReturn() используется конструкция doAction().when(). Это связано с тем, что компилятор Java не может определить тип возвращаемого значения для void методов в цепочке when().
Основные подходы к работе с void методами:
- doNothing() — метод выполняется без побочных эффектов
- doThrow() — метод бросает исключение
- doAnswer() — кастомная логика выполнения
- doCallRealMethod() — вызов реального метода
Пошаговая настройка тестового окружения
Начнём с создания базового проекта. Для работы с Mockito понадобится добавить зависимость в Maven или Gradle:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
Создадим простой сервис для работы с пользователями:
public class UserService {
private final EmailService emailService;
private final DatabaseService databaseService;
public UserService(EmailService emailService, DatabaseService databaseService) {
this.emailService = emailService;
this.databaseService = databaseService;
}
public void createUser(String email, String name) {
User user = new User(email, name);
databaseService.saveUser(user);
emailService.sendWelcomeEmail(email, name);
}
public void deleteUser(String email) {
databaseService.deleteUser(email);
emailService.sendGoodbyeEmail(email);
}
}
И соответствующие зависимости:
public interface EmailService {
void sendWelcomeEmail(String email, String name);
void sendGoodbyeEmail(String email);
}
public interface DatabaseService {
void saveUser(User user);
void deleteUser(String email);
}
Базовые примеры мокирования
Теперь создадим тесты для нашего сервиса:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private EmailService emailService;
@Mock
private DatabaseService databaseService;
@InjectMocks
private UserService userService;
@Test
void shouldCreateUserSuccessfully() {
// Given
String email = "test@example.com";
String name = "John Doe";
// When
userService.createUser(email, name);
// Then
verify(databaseService).saveUser(any(User.class));
verify(emailService).sendWelcomeEmail(email, name);
}
@Test
void shouldHandleDatabaseException() {
// Given
String email = "test@example.com";
String name = "John Doe";
doThrow(new RuntimeException("Database error"))
.when(databaseService).saveUser(any(User.class));
// When & Then
assertThrows(RuntimeException.class, () -> {
userService.createUser(email, name);
});
verify(databaseService).saveUser(any(User.class));
verify(emailService, never()).sendWelcomeEmail(anyString(), anyString());
}
}
Продвинутые техники и кейсы
Рассмотрим более сложные сценарии использования:
@Test
void shouldExecuteMethodsInCorrectOrder() {
// Given
String email = "test@example.com";
// When
userService.deleteUser(email);
// Then
InOrder inOrder = inOrder(databaseService, emailService);
inOrder.verify(databaseService).deleteUser(email);
inOrder.verify(emailService).sendGoodbyeEmail(email);
}
@Test
void shouldHandleEmailServiceFailure() {
// Given
String email = "test@example.com";
String name = "John Doe";
doNothing().when(databaseService).saveUser(any(User.class));
doThrow(new EmailException("SMTP server unavailable"))
.when(emailService).sendWelcomeEmail(email, name);
// When & Then
assertThrows(EmailException.class, () -> {
userService.createUser(email, name);
});
verify(databaseService).saveUser(any(User.class));
verify(emailService).sendWelcomeEmail(email, name);
}
@Test
void shouldCaptureArgumentsInVoidMethod() {
// Given
String email = "test@example.com";
String name = "John Doe";
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
// When
userService.createUser(email, name);
// Then
verify(databaseService).saveUser(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertEquals(email, capturedUser.getEmail());
assertEquals(name, capturedUser.getName());
}
Использование doAnswer для сложной логики
Когда нужна более сложная логика, используй doAnswer():
@Test
void shouldExecuteCustomLogicInVoidMethod() {
// Given
String email = "test@example.com";
String name = "John Doe";
AtomicBoolean emailSent = new AtomicBoolean(false);
doAnswer(invocation -> {
String emailArg = invocation.getArgument(0);
String nameArg = invocation.getArgument(1);
// Кастомная логика
if (emailArg.contains("@")) {
emailSent.set(true);
System.out.println("Email sent to: " + emailArg);
}
return null;
}).when(emailService).sendWelcomeEmail(anyString(), anyString());
// When
userService.createUser(email, name);
// Then
assertTrue(emailSent.get());
verify(emailService).sendWelcomeEmail(email, name);
}
Сравнение подходов к тестированию void методов
Подход | Преимущества | Недостатки | Когда использовать |
---|---|---|---|
doNothing() | Простота, быстродействие | Не проверяет логику | Методы без побочных эффектов |
doThrow() | Тестирование исключений | Только негативные сценарии | Обработка ошибок |
doAnswer() | Полный контроль логики | Сложность, медленнее | Сложные сценарии |
verify() | Проверка вызовов | Не контролирует выполнение | Проверка взаимодействий |
Типичные ошибки и как их избежать
Самые частые проблемы при работе с void методами:
- Использование when() вместо doAction() — приводит к ошибкам компиляции
- Забывание verify() — тесты проходят, но не проверяют взаимодействие
- Неправильный порядок вызовов — используй InOrder для проверки последовательности
- Игнорирование исключений — всегда тестируй negative cases
Неправильно:
// Не компилируется!
when(emailService.sendWelcomeEmail(anyString(), anyString())).thenReturn(null);
Правильно:
doNothing().when(emailService).sendWelcomeEmail(anyString(), anyString());
Интеграция с другими инструментами
Mockito отлично работает с другими testing framework’ами:
- Spring Boot Test — используй @MockBean для интеграционных тестов
- TestContainers — комбинируй моки с реальными базами данных
- WireMock — мокирование HTTP-сервисов совместно с void методами
- PowerMock — для статических и финальных методов (deprecated, лучше рефакторить)
Для серверных приложений особенно полезно сочетание Mockito с VPS для CI/CD пайплайнов и выделенными серверами для нагрузочного тестирования.
Автоматизация и скрипты
Создай Maven-профиль для запуска только тестов с моками:
<profile>
<id>mock-tests</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*MockTest.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
И bash-скрипт для автоматизации:
#!/bin/bash
echo "Running mock tests..."
mvn test -Pmock-tests
if [ $? -eq 0 ]; then
echo "✅ All mock tests passed!"
else
echo "❌ Some tests failed"
exit 1
fi
Статистика и бенчмарки
По данным JVM Ecosystem Report 2023, Mockito используется в 78% Java-проектов. Тесты с моками выполняются в среднем в 10-15 раз быстрее интеграционных тестов. Покрытие кода с void методами обычно составляет 60-70% от общего покрытия в enterprise-приложениях.
Альтернативы Mockito для void методов:
- EasyMock — более verbose синтаксис
- PowerMock — для статических методов (устарел)
- JMockit — более мощный, но сложнее в изучении
- Spock — для Groovy/Kotlin проектов
Интересные факты и нестандартные применения
Знал ли ты, что Mockito может мокировать даже финальные классы начиная с версии 2.0? Это открывает возможности для тестирования legacy-кода без рефакторинга.
Необычные способы использования:
- Тестирование логгирования — мокируй Logger для проверки сообщений
- Профилирование производительности — используй doAnswer() для измерения времени выполнения
- Симуляция медленных операций — добавляй Thread.sleep() в doAnswer()
- Тестирование retry-логики — выбрасывай исключения на первых вызовах
Полезные ссылки:
Заключение и рекомендации
Mockito для void методов — это мощный инструмент, который должен быть в арсенале каждого Java-разработчика. Основные принципы успешного использования:
- Используй doAction().when() синтаксис для void методов
- Всегда проверяй взаимодействие с помощью verify()
- Тестируй как позитивные, так и негативные сценарии
- Контролируй порядок вызовов с InOrder
- Не забывай про ArgumentCaptor для сложных проверок
Для production-окружений рекомендую использовать моки в unit-тестах (быстро и изолированно) и интеграционные тесты с реальными сервисами для критичных бизнес-процессов. Это обеспечит баланс между скоростью разработки и надёжностью системы.
Помни: хорошие тесты — это не только высокое покрытие кода, но и понятность того, что именно тестируется. Void методы часто содержат критически важную бизнес-логику, и их правильное тестирование может спасти от серьёзных багов в production.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.