Урок по треку Java 7 мин

Тестирование: JUnit 5 и Mockito

Структура тестов JUnit 5, параметризованные тесты, моки и стабы в Mockito, что и как тестировать.

На собесе по тестированию спрашивают не аннотации наизусть, а понимание: что такое юнит-тест, где мок, а где стаб, как тестировать код со временем и случайностью, почему тесты «мигают».

Пирамида тестов

  • Юнит-тесты: один класс или функция, без сети и БД, миллисекунды. Их больше всего.
  • Интеграционные: несколько компонентов вместе, часто с настоящей БД в Testcontainers.
  • End-to-end: система целиком через API или UI. Мало, дорогие и медленные.

JUnit 5

class PriceCalculatorTest {
    private PriceCalculator calc;

    @BeforeEach
    void setUp() { calc = new PriceCalculator(); }

    @Test
    void appliesDiscountForLargeOrders() {
        // given
        var order = new Order(BigDecimal.valueOf(10_000));
        // when
        var price = calc.total(order);
        // then
        assertEquals(new BigDecimal("9000.00"), price);
    }

    @Test
    void rejectsNegativeAmount() {
        var ex = assertThrows(IllegalArgumentException.class,
            () -> calc.total(new Order(BigDecimal.valueOf(-1))));
        assertEquals("amount < 0", ex.getMessage());
    }
}
  • Жизненный цикл: @BeforeAll/@AfterAll один раз на класс (статические методы по умолчанию), @BeforeEach/@AfterEach вокруг каждого теста. JUnit по умолчанию создаёт новый экземпляр класса на каждый тест, поэтому поля не делят состояние между тестами.
  • @ParameterizedTest с @ValueSource, @CsvSource, @MethodSource: один тест на много входов.
  • @Nested группирует сценарии, @DisplayName даёт читаемые имена, @Disabled выключает.
  • assertAll проверяет несколько условий и показывает все провалы сразу.
  • assertThrows возвращает исключение, его можно проверить дальше.
  • Порядок тестов не гарантирован, и тесты не должны от него зависеть.

Многие команды используют AssertJ: assertThat(list).hasSize(3).contains("a") читается лучше.

Mockito

Зависимости класса подменяют, чтобы тестировать его изолированно.

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock PaymentClient payments;
    @Mock OrderRepository repo;
    @InjectMocks OrderService service;

    @Test
    void paysAndSavesOrder() {
        when(payments.charge(any(), eq(500L))).thenReturn(new Receipt("r1"));

        service.checkout(new Cart(500L));

        verify(repo).save(argThat(o -> o.receiptId().equals("r1")));
        verifyNoMoreInteractions(payments);
    }
}
  • @Mock создаёт заглушку: методы возвращают значения по умолчанию (null, 0, пустые коллекции, пустой Optional).
  • when(...).thenReturn(...) задаёт поведение (стаб), thenThrow для ошибок.
  • verify(mock).method(...) проверяет вызов (мок в узком смысле), times(2), never().
  • ArgumentCaptor вытаскивает переданный аргумент для проверок.
  • @Spy оборачивает настоящий объект: по умолчанию вызываются реальные методы. Для spy стаб пишут через doReturn(x).when(spy).method(), иначе when(spy.method()) сначала вызовет реальный метод.
  • Матчеры: если один аргумент через any(), остальные тоже должны быть матчерами (eq(500L)), иначе InvalidUseOfMatchersException.
  • Со строгим режимом MockitoExtension падает на неиспользованных стабах (UnnecessaryStubbingException): это помогает держать тесты чистыми.
  • final классы и статические методы мокаются (inline mock maker стал по умолчанию в Mockito 5, статика через mockStatic), но часто это признак, что зависимость стоит вынести за интерфейс.

Хороший тест

  • Проверяет поведение, а не реализацию: не нужно verify на каждый внутренний вызов.
  • Детерминирован: время через Clock, случайность через сид или внедрённый генератор, никакого Thread.sleep для ожидания асинхронщины (лучше Awaitility).
  • Независим от других тестов и порядка.
  • Один сценарий на тест, понятное имя.

Что спросят на собесе

  • Чем юнит-тест отличается от интеграционного? Что такое пирамида тестов?
  • Мок, стаб, spy: в чём разница?
  • Как протестировать метод, который использует LocalDateTime.now()?
  • Как тестировать исключения и параметризованные случаи в JUnit 5?
  • Почему тесты бывают нестабильными и как это лечить?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.