- Home »

Учебник Mockito — основы мокирования в Java
Тестирование Java-приложений — штука без которой не выживешь в современной разработке. Если работаешь с серверными приложениями, микросервисами или просто автоматизируешь инфраструктуру через Java, то наверняка сталкивался с проблемой: как протестировать код, который зависит от внешних сервисов, баз данных или других компонентов? Вот тут-то и спасает Mockito — мощный фреймворк для создания моков (заглушек) в Java. Он позволяет изолировать тестируемый код от внешних зависимостей и создавать предсказуемые тестовые сценарии. Особенно полезно это для серверных приложений, где нужно тестировать API endpoints, сервисы баз данных и интеграции с внешними системами.
Как работает Mockito
Mockito создает прокси-объекты, которые имитируют поведение реальных классов. Под капотом используется байткод-манипуляция через библиотеку ByteBuddy (в старых версиях — cglib). Когда ты вызываешь метод на моке, Mockito перехватывает вызов и возвращает заранее настроенный результат или выполняет заданное действие.
Основные концепции:
- Mock — полностью поддельный объект, все методы возвращают null/0/false по умолчанию
- Spy — обёртка вокруг реального объекта, позволяет перехватывать отдельные методы
- Stub — настройка поведения мока (what to return when method is called)
- Verify — проверка того, что определенные методы были вызваны
Быстрая настройка Mockito
Для Maven добавь зависимость в pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
Для Gradle:
testImplementation 'org.mockito:mockito-core:5.7.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.7.0'
Базовая настройка тестового класса:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
@Mock
private DatabaseService databaseService;
@Test
void testUserCreation() {
// твои тесты здесь
}
}
Практические примеры: от простого к сложному
Базовое мокирование
Допустим, у тебя есть сервис для работы с пользователями, который зависит от базы данных:
// Сервис базы данных
interface DatabaseService {
User findUserById(Long id);
void saveUser(User user);
boolean userExists(String email);
}
// Основной сервис
class UserService {
private final DatabaseService databaseService;
public UserService(DatabaseService databaseService) {
this.databaseService = databaseService;
}
public User createUser(String email, String name) {
if (databaseService.userExists(email)) {
throw new UserAlreadyExistsException("User with email " + email + " already exists");
}
User user = new User(email, name);
databaseService.saveUser(user);
return user;
}
}
Тестируем без реальной базы данных:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private DatabaseService databaseService;
@Test
void shouldCreateUserWhenEmailNotExists() {
// Arrange
String email = "test@example.com";
String name = "Test User";
when(databaseService.userExists(email)).thenReturn(false);
UserService userService = new UserService(databaseService);
// Act
User result = userService.createUser(email, name);
// Assert
assertThat(result.getEmail()).isEqualTo(email);
assertThat(result.getName()).isEqualTo(name);
verify(databaseService).userExists(email);
verify(databaseService).saveUser(any(User.class));
}
@Test
void shouldThrowExceptionWhenUserExists() {
// Arrange
String email = "existing@example.com";
when(databaseService.userExists(email)).thenReturn(true);
UserService userService = new UserService(databaseService);
// Act & Assert
assertThatThrownBy(() -> userService.createUser(email, "Name"))
.isInstanceOf(UserAlreadyExistsException.class)
.hasMessage("User with email existing@example.com already exists");
verify(databaseService).userExists(email);
verify(databaseService, never()).saveUser(any(User.class));
}
}
Мокирование HTTP-клиентов
Для серверной разработки часто нужно тестировать интеграции с внешними API. Рассмотрим пример с HTTP-клиентом:
class ExternalApiService {
private final HttpClient httpClient;
public ExternalApiService(HttpClient httpClient) {
this.httpClient = httpClient;
}
public ServerInfo getServerStatus(String serverUrl) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serverUrl + "/status"))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return parseServerInfo(response.body());
} else {
throw new ServerUnavailableException("Server returned: " + response.statusCode());
}
} catch (Exception e) {
throw new ServerUnavailableException("Failed to connect to server", e);
}
}
}
Тестируем с мокированным HTTP-клиентом:
@ExtendWith(MockitoExtension.class)
class ExternalApiServiceTest {
@Mock
private HttpClient httpClient;
@Mock
private HttpResponse<String> httpResponse;
@Test
void shouldReturnServerInfoWhenStatusOk() throws Exception {
// Arrange
String serverUrl = "https://api.example.com";
String responseBody = "{\"status\":\"online\",\"cpu\":25,\"memory\":60}";
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)))
.thenReturn(httpResponse);
when(httpResponse.statusCode()).thenReturn(200);
when(httpResponse.body()).thenReturn(responseBody);
ExternalApiService service = new ExternalApiService(httpClient);
// Act
ServerInfo result = service.getServerStatus(serverUrl);
// Assert
assertThat(result.getStatus()).isEqualTo("online");
assertThat(result.getCpuUsage()).isEqualTo(25);
ArgumentCaptor<HttpRequest> requestCaptor = ArgumentCaptor.forClass(HttpRequest.class);
verify(httpClient).send(requestCaptor.capture(), any(HttpResponse.BodyHandler.class));
HttpRequest capturedRequest = requestCaptor.getValue();
assertThat(capturedRequest.uri().toString()).isEqualTo(serverUrl + "/status");
assertThat(capturedRequest.method()).isEqualTo("GET");
}
@Test
void shouldThrowExceptionWhenServerReturns500() throws Exception {
// Arrange
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)))
.thenReturn(httpResponse);
when(httpResponse.statusCode()).thenReturn(500);
ExternalApiService service = new ExternalApiService(httpClient);
// Act & Assert
assertThatThrownBy(() -> service.getServerStatus("https://api.example.com"))
.isInstanceOf(ServerUnavailableException.class)
.hasMessage("Server returned: 500");
}
}
Продвинутые техники мокирования
Использование Answer для сложной логики
Иногда нужно имитировать более сложное поведение, чем просто возврат значения:
@Test
void shouldSimulateAsyncCallback() {
// Имитируем асинхронную обработку
doAnswer(invocation -> {
String message = invocation.getArgument(0);
Callback callback = invocation.getArgument(1);
// Симулируем задержку
Thread.sleep(100);
if (message.contains("error")) {
callback.onError("Processing failed");
} else {
callback.onSuccess("Message processed: " + message);
}
return null;
}).when(messageProcessor).processAsync(anyString(), any(Callback.class));
}
Spy для частичного мокирования
Spy позволяет мокировать только часть методов реального объекта:
@Test
void shouldUseSpyForPartialMocking() {
// Создаем spy на основе реального объекта
FileService fileService = spy(new FileService());
// Мокируем только метод чтения файла
doReturn("mocked content").when(fileService).readFile("/etc/config.txt");
// Остальные методы работают как обычно
ConfigManager configManager = new ConfigManager(fileService);
Config config = configManager.loadConfig();
assertThat(config.getContent()).isEqualTo("mocked content");
// Проверяем, что метод был вызван
verify(fileService).readFile("/etc/config.txt");
}
Сравнение с другими решениями
Фреймворк | Простота использования | Производительность | Функциональность | Активность развития |
---|---|---|---|---|
Mockito | Высокая | Хорошая | Богатая | Активная |
EasyMock | Средняя | Хорошая | Средняя | Низкая |
PowerMock | Низкая | Низкая | Очень богатая | Устарел |
JMock | Низкая | Хорошая | Средняя | Низкая |
Интеграция с другими инструментами
Mockito + TestContainers
Для тестирования серверных приложений часто комбинируют Mockito с TestContainers:
@Testcontainers
class IntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Mock
private ExternalApiService externalApiService;
@Test
void shouldTestWithRealDbAndMockedApi() {
// Реальная база данных в контейнере
DatabaseService databaseService = new DatabaseService(postgres.getJdbcUrl());
// Мокированный внешний API
when(externalApiService.validateUser(anyString())).thenReturn(true);
UserService userService = new UserService(databaseService, externalApiService);
// Тестируем с реальной БД и мокированным API
User user = userService.createUser("test@example.com", "Test User");
assertThat(user.getId()).isNotNull();
verify(externalApiService).validateUser("test@example.com");
}
}
Автоматизация с помощью Maven/Gradle
Для CI/CD пайплайнов можно настроить автоматический запуск тестов:
# Maven
mvn clean test -Dtest.profile=mock
# Gradle
./gradlew test --tests "*MockTest"
Интересные факты и нестандартные применения
- Mockito используется в 85% Java-проектов на GitHub согласно исследованию 2023 года
- Можно мокировать final классы и методы с помощью mock-maker-inline
- Mockito может работать с Android-приложениями через dexmaker
- Статические методы можно мокировать с Mockito 3.4+ используя mockStatic()
Пример мокирования статических методов:
@Test
void shouldMockStaticMethod() {
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
mockedFiles.when(() -> Files.readString(any(Path.class)))
.thenReturn("mocked file content");
String result = ConfigLoader.loadFromFile("/path/to/config.txt");
assertThat(result).isEqualTo("mocked file content");
}
}
Автоматизация и DevOps
Mockito отлично подходит для автоматизации тестирования серверных приложений. Если разворачиваешь Java-сервисы на VPS или выделенном сервере, то можешь использовать Mockito для:
- Тестирования deployment-скриптов — мокируй файловую систему и системные вызовы
- Проверки мониторинга — имитируй различные состояния сервера
- Тестирования балансировщиков — мокируй ответы от backend-серверов
- Валидации конфигураций — проверяй обработку различных настроек
Скрипт для автоматического запуска тестов на сервере:
#!/bin/bash
# run-tests.sh
# Проверяем доступность тестовой базы данных
if ! nc -z localhost 5432; then
echo "Starting test database..."
docker run -d --name test-db -p 5432:5432 -e POSTGRES_PASSWORD=test postgres:14
sleep 10
fi
# Запускаем тесты с мокированными внешними сервисами
mvn test -Dspring.profiles.active=test,mock
# Генерируем отчёт о покрытии
mvn jacoco:report
# Отправляем результаты в систему мониторинга
curl -X POST http://monitoring.example.com/test-results \
-H "Content-Type: application/json" \
-d @target/jacoco-report/jacoco.json
Производительность и оптимизация
Несколько советов для оптимизации тестов с Mockito:
- Переиспользуй моки — создавай их в @BeforeEach, а не в каждом тесте
- Используй @MockBean осторожно — в Spring Boot это пересоздаёт контекст
- Ограничивай область мокирования — не мокируй всё подряд
- Избегай deep stubs — лучше создать несколько простых моков
Пример оптимизированного теста:
@ExtendWith(MockitoExtension.class)
class OptimizedTest {
@Mock
private DatabaseService databaseService;
@Mock
private CacheService cacheService;
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserService(databaseService, cacheService);
// Общие настройки для всех тестов
when(cacheService.isEnabled()).thenReturn(true);
}
@Test
void testMethod1() {
// Специфичные настройки только для этого теста
when(databaseService.findUser(1L)).thenReturn(new User("test"));
// Тест...
}
}
Заключение и рекомендации
Mockito — незаменимый инструмент для любого Java-разработчика, особенно если работаешь с серверными приложениями. Он позволяет создавать быстрые, надёжные и изолированные тесты, что критически важно для CI/CD процессов.
Используй Mockito когда:
- Тестируешь бизнес-логику без внешних зависимостей
- Нужно имитировать сложные интеграции (API, базы данных)
- Проверяешь взаимодействие между компонентами
- Разрабатываешь микросервисы с множественными зависимостями
Не используй Mockito когда:
- Тестируешь простые POJO или утилитарные классы
- Проверяешь интеграцию с реальными системами (лучше integration tests)
- Мокирование становится сложнее, чем сам код
Для серверных приложений рекомендую комбинировать Mockito с TestContainers для комплексного тестирования: моки для внешних API, контейнеры для баз данных. Это даёт оптимальный баланс между скоростью выполнения тестов и их надёжностью.
Полезные ссылки для изучения:
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.