Home » Учебник Mockito — основы мокирования в Java
Учебник Mockito — основы мокирования в Java

Учебник 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, контейнеры для баз данных. Это даёт оптимальный баланс между скоростью выполнения тестов и их надёжностью.

Полезные ссылки для изучения:


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked