Mock-объекты и фикстуры в Java: как тестировать быстрее и умнее

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему ваши Java-тесты ломаются (и как это исправить)

Вы написали идеальный код. Сервис работает, логика безупречна, а баги — только в чужих проектах. Но когда запускаете тесты... Там — ошибка подключения к БД, тут — внешний API не отвечает, а в третьем тесте — вообще NullPointerException, потому что конфигурация поднялась не так. Знакомо?

Проблема в том, что классические тесты зависят от внешнего окружения. База данных, REST-сервисы, файловая система — всё это делает тесты хрупкими, медленными и непредсказуемыми. Решение — mock-объекты и фикстуры. Это не просто модные слова, а инструменты, которые превращают хаос в порядок.

В этой статье мы разберёмся, как правильно использовать mock и фикстуры в Java-тестах на реальных примерах. Вы узнаете, чем stub отличается от mock, как настраивать фикстуры для сложных сценариев и почему Mockito — ваш лучший друг. Поехали!

Mock-объекты: симуляция без боли

Что такое mock и зачем он нужен?

Mock-объект — это имитация реального компонента, которая ведёт себя так, как вы скажете. Вместо того чтобы поднимать базу данных или вызывать внешний API, вы создаёте «фальшивку», которая возвращает заранее заданные данные.

Представьте: вы тестируете сервис, который отправляет email-уведомления. Для настоящего теста нужно поднять почтовый сервер, настроить SMTP, проверить очередь... А с mock-объектом вы просто проверяете, что сервис вызвал метод отправки с правильными параметрами. Быстро, чисто, контролируемо.

Mock vs Stub: в чём разница?

Новички часто путают эти понятия. Давайте расставим точки над i:

  • Stub — возвращает фиксированные данные. Вы говорите: «При вызове метода getUser() верни объект User с именем "John"». Stub не проверяет, сколько раз его вызвали.
  • Mock — не только возвращает данные, но и проверяет поведение. Вы можете сказать: «Убедись, что метод sendEmail() был вызван ровно один раз с конкретным адресом».

Проще говоря: stub — это «дай мне данные», mock — «дай мне данные и проверь, как меня используют». На практике чаще всего используют mock-объекты, так как они дают больше контроля.

Пишем первый mock с Mockito

Mockito — самая популярная библиотека для моков в Java. Она интегрируется с JUnit 5 и Spring Boot, а синтаксис настолько простой, что вы запомните его за пять минут.

Допустим, у вас есть сервис OrderService, который зависит от репозитория OrderRepository. Вместо реального репозитория мы создадим mock:

import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {

    @Mock
    private OrderRepository orderRepository;

    @Test
    void testCreateOrder() {
        // Создаём mock-объект
        OrderService orderService = new OrderService(orderRepository);
        
        // Настраиваем поведение mock
        when(orderRepository.save(any(Order.class)))
            .thenReturn(new Order(1L, "test@example.com", 100.0));
        
        // Вызываем тестируемый метод
        Order result = orderService.createOrder("test@example.com", 100.0);
        
        // Проверяем результат
        assertThat(result.getId()).isEqualTo(1L);
        // Проверяем, что метод save был вызван ровно один раз
        verify(orderRepository, times(1)).save(any(Order.class));
    }
}

В этом примере мы:

  • Создали mock для OrderRepository с помощью аннотации @Mock.
  • Настроили, что при вызове save() должен возвращаться заранее созданный заказ.
  • Проверили, что метод действительно был вызван (верификация).

Обратите внимание: мы не трогаем реальную базу данных. Тест летит за миллисекунды, а не за секунды.

Фикстуры: готовим данные для тестов

Что такое фикстуры и зачем они нужны?

Фикстуры (test fixtures) — это предопределённое состояние данных, которое нужно для выполнения теста. Например, если вы тестируете поиск товаров по категории, вам нужно, чтобы в базе уже были какие-то товары. Фикстуры решают эту задачу, создавая «подопытные» объекты до запуска теста.

Зачем это нужно? Во-первых, чтобы не дублировать код инициализации в каждом тесте. Во-вторых, чтобы гарантировать, что каждый тест стартует с одинаковыми данными — никаких сюрпризов от предыдущих запусков.

Способы создания фикстур в Java

Есть несколько подходов, от простых к сложным:

  • Ручное создание в методе @BeforeEach — классика JUnit. Подходит для небольших проектов.
  • Object Mother — паттерн, при котором вы создаёте фабричные методы для часто используемых объектов.
  • Библиотеки для фикстур — например, Instancio или EasyRandom, которые генерируют объекты автоматически.

Рассмотрим каждый вариант на примере класса User с полями id, name, email и role.

Вариант 1: @BeforeEach (простой, но много кода)

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class UserServiceTest {

    private User adminUser;
    private User regularUser;

    @BeforeEach
    void setUp() {
        adminUser = new User(1L, "Alice", "alice@example.com", Role.ADMIN);
        regularUser = new User(2L, "Bob", "bob@example.com", Role.USER);
    }

    @Test
    void testAdminCanDeleteUser() {
        UserService userService = new UserService();
        assertTrue(userService.canDeleteUser(adminUser));
    }

    @Test
    void testRegularUserCannotDeleteUser() {
        UserService userService = new UserService();
        assertFalse(userService.canDeleteUser(regularUser));
    }
}

Этот метод работает, но если у вас 10 классов с разными состояниями, код быстро разрастается.

Вариант 2: Object Mother (чище и переиспользуемо)

Создаём отдельный класс-фабрику:

public class UserFixture {
    public static User createAdmin() {
        return new User(1L, "Alice", "alice@example.com", Role.ADMIN);
    }

    public static User createRegularUser() {
        return new User(2L, "Bob", "bob@example.com", Role.USER);
    }

    public static User createUserWithRole(Role role) {
        return new User(null, "Default", "                

Блоги

Рекомендаций по книгам