Перейти к основному содержимому

JUnit 5 и тестирование Java

Разработчику Архитектору

JUnit 5 и тестирование Java

Тесты — это способ не бояться менять код. Хороший набор тестов отвечает на вопрос: "Я сломал что-то важное?" — за секунды, до деплоя.

В Java-мире базовая тройка такая:

УровеньИнструментЧто проверяем
UnitJUnit 5 + MockitoОдин класс, зависимости подменены
Web-слойMockMvcКонтроллер без поднятия Tomcat
Интеграция@SpringBootTest, TestcontainersКонтекст + БД в Docker

JUnit 5 (модуль Jupiter) — стандарт unit-тестов. Сборка: Maven / Gradle. Spring: Первая программа на Spring Framework, Security, JWT, ошибки API.

С чего начать

Напишите тест на чистую функцию (Calculator ниже), запустите mvn test, только потом подключайте Mockito и MockMvc.


JUnit 5 — основы

Зависимость Maven:

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

Разбор:

  • этот XML-блок описывает конфигурацию сборки, которую инструмент считывает до выполнения прикладного Java-кода.
  • элементы с артефактами junit-jupiter включают нужные библиотеки и определяют доступные API в кодовой базе.
  • секция <scope>test</scope> ограничивает зависимость тестовым контуром и не тащит ее в production-артефакт.
  • любое изменение здесь влияет на classpath и поведение сборки, поэтому такие правки проверяют полным прогоном тестов.
  • после редактирования этого фрагмента проверяют, что проект собирается и тестовый раннер видит нужные классы без конфликтов версий.

Код ITЗагрузка примера кода…

Разбор:

  • фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
  • аннотации Test, DisplayName, ParameterizedTest, CsvSource включают поведение фреймворка (тестовый раннер, web-маршрутизация, ORM или транзакционные правила).
  • ключевые вызовы Calculator, DisplayName, adds, assertEquals, add, CsvSource, addsParameterized задают основную механику этого примера: обработку, проверку и формирование результата.
  • проверки через assert... фиксируют ожидаемое поведение как контракт, который автоматически проверяется на каждом прогоне тестов.
  • после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
  • при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.

Запуск:

mvn test
# или
./mvnw test

Разбор:

  • команды в этом фрагменте выполняются по шагам и составляют практический workflow — запуск, тесты, сборка или проверка окружения.
  • последовательность mvn test, ./mvnw test покрывает ключевые этапы жизненного цикла проекта в CLI.
  • если одна команда завершается ошибкой, последующие шаги не подтверждают корректность сценария, пока не устранена первопричина.
  • результат смотрят по exit code и по логам команды: успешный статус, ожидаемые артефакты и отсутствие stack trace.
  • такой же набор команд обычно фиксируют в CI, чтобы локальная проверка и pipeline давали одинаковый результат.

Разбор аннотаций:

  • @Test — метод-тест; без него JUnit не запустит метод.
  • @DisplayName — человекочитаемое имя в отчёте IDE/CI.
  • @ParameterizedTest + @CsvSource — один тест, много входных строк (меньше копипасты).
  • assertThrows — ожидаемое исключение (важно для валидации и деления на ноль).

Класс Calculator для примера — обычный Java без Spring; тесты лежат в src/test/java в том же пакете.


Mockito

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

Разбор:

  • этот XML-блок описывает конфигурацию сборки, которую инструмент считывает до выполнения прикладного Java-кода.
  • элементы с артефактами mockito-junit-jupiter включают нужные библиотеки и определяют доступные API в кодовой базе.
  • секция <scope>test</scope> ограничивает зависимость тестовым контуром и не тащит ее в production-артефакт.
  • любое изменение здесь влияет на classpath и поведение сборки, поэтому такие правки проверяют полным прогоном тестов.
  • после редактирования этого фрагмента проверяют, что проект собирается и тестовый раннер видит нужные классы без конфликтов версий.

Код ITЗагрузка примера кода…

Разбор:

  • фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
  • аннотации ExtendWith, Mock, InjectMocks, Test включают поведение фреймворка (тестовый раннер, web-маршрутизация, ORM или транзакционные правила).
  • ключевые вызовы ExtendWith, createsUser, when, existsByEmail, thenReturn, save, any задают основную механику этого примера: обработку, проверку и формирование результата.
  • проверки через assert... фиксируют ожидаемое поведение как контракт, который автоматически проверяется на каждом прогоне тестов.
  • после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
  • при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.

Разбор: @Mock — заглушка; @InjectMocks — класс под тестом, в него подставят моки. when(...).thenReturn(...) задаёт поведение; verify проверяет, что метод вызвали. Так тестируют сервис без реальной БД.


Spring Boot Test

В проекте Spring Boot достаточно spring-boot-starter-test (JUnit + Mockito + AssertJ + MockMvc).


@WebMvcTest — контроллер изолированно

Поднимается только веб-слой (контроллер + JSON), без полной БД. Быстро и подходит для проверки Ошибки REST — @Valid и @ControllerAdvice — валидации.

@WebMvcTest(HelloController.class)
class HelloControllerTest {

@Autowired
MockMvc mvc;

@Test
void returnsGreeting() throws Exception {
mvc.perform(get("/api/hello").param("name", "Java"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Hello, Java"));
}
}

Разбор:

  • фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
  • аннотации WebMvcTest, Autowired, Test включают поведение фреймворка (тестовый раннер, web-маршрутизация, ORM или транзакционные правила).
  • ключевые вызовы WebMvcTest, returnsGreeting, perform, get, param, andExpect, status задают основную механику этого примера: обработку, проверку и формирование результата.
  • чтение этого блока по шагам дает предсказуемую картину — сначала подготовка данных, затем действие API, затем получение конечного значения.
  • после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
  • при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.

jsonPath — как XPath для JSON: достаёт поле из тела ответа.


@SpringBootTest — полный контекст

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApplicationIT {

@Autowired
TestRestTemplate rest;

@Test
void health() {
var response = rest.getForEntity("/actuator/health", String.class);
assertEquals(200, response.getStatusCode().value());
}
}

Разбор:

  • фрагмент показывает прикладной сценарий по теме раздела: здесь есть входные данные, операции над ними и проверяемый итог.
  • аннотации SpringBootTest, Autowired, Test включают поведение фреймворка (тестовый раннер, web-маршрутизация, ORM или транзакционные правила).
  • ключевые вызовы SpringBootTest, health, getForEntity, assertEquals, getStatusCode, value задают основную механику этого примера: обработку, проверку и формирование результата.
  • проверки через assert... фиксируют ожидаемое поведение как контракт, который автоматически проверяется на каждом прогоне тестов.
  • после запуска стоит проверить именно эффект этого примера — вывод, значение переменной, статус ответа, состояние БД или результат теста.
  • при переносе в production обычно добавляют валидацию входа, явную обработку ошибок и тест на граничные случаи вокруг этого сценария.

Структура тестов Maven

src/
main/java/...
test/java/... # зеркало пакетов main

Разбор:

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

Имена: *Test.java (unit), *IT.java (integration, опционально отдельный профиль).


CI

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- run: mvn -B verify

Разбор:

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

Тестовая пирамида на практике

Удобная стратегия для реального проекта:

  • много unit-тестов (JUnit 5 + Mockito) — быстрый feedback на бизнес-логику;
  • меньше web-тестов (MockMvc) — проверка контрактов контроллеров;
  • немного интеграционных тестов (@SpringBootTest, Testcontainers) — проверка "склейки" с БД и инфраструктурой.

Если пирамида перевёрнута (почти всё на @SpringBootTest), сборка становится медленной и команда начинает реже запускать тесты локально.


Тестирование конкурентного кода

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

Минимальный набор:

  1. Тест на таймаут (orTimeout, get(timeout, ...)) для внешних вызовов.
  2. Тест на отмену (future.cancel(true)) и корректную обработку InterruptedException.
  3. Повторяемый стресс-кейс (цикл запусков) для поиска race condition.

Практика стабильности:

  • избегайте "голых" Thread.sleep(...) без явного условия;
  • для ожиданий лучше использовать await-подход и фиксированные дедлайны;
  • если тест "плавает", сначала уберите зависимость от реального времени и планировщика ОС.

Расшифровка терминов:

  • Race condition — итог зависит от случайного порядка выполнения потоков.
  • Flaky test — тест иногда падает без изменений в коде.
  • Deadline — максимальное время ожидания, после которого тест завершает шаг с ошибкой.

Связанные главы:


Минимальный план для нового модуля

  1. На каждый публичный метод сервиса — 2-5 unit-тестов (happy path + edge cases).
  2. На каждый внешний HTTP-контракт — минимум один MockMvc тест со статусом и JSON-полями.
  3. На каждый репозиторий или SQL-слой — 1-2 интеграционных теста на реальной БД.
  4. На критический пользовательский сценарий — один end-to-end тест в CI.

Частые антипаттерны и как исправлять

АнтипаттернПочему плохоЧто делать
Один большой тест "проверяет всё сразу"Трудно понять, что сломалосьДелить на узкие тесты по одному поведению
Проверка только status().isOk()Контракт ответа может сломаться незаметноДобавлять jsonPath и проверять ключевые поля
Много Thread.sleep(...) в тестахНестабильность и долгий прогонИспользовать await-подходы (Awaitility) и явные условия
Моки для всего, включая слой БДНе видно проблем SQL/маппингаДобавить интеграционные тесты с Testcontainers
Случайные данные без фиксированного seedТесты "плавают"Фиксировать seed и значения входов
Связанные материалы

Для API-валидации и ошибок комбинируйте подходы из Ошибки REST, Spring Security и JWT.


Частые ошибки

СимптомПричина
Тесты не запускаютсяЗависимость без scope=test
NullPointerException в тесте@InjectMocks без @Mock для зависимости
MockMvc 404Не тот @WebMvcTest(Controller.class)
Медленные "unit"-тестыПоднят лишний @SpringBootTest

Что попробовать

  1. Тест на глобальный обработчик ошибокPOST с пустым телом → 400.
  2. @WithMockUser вместе с Security для закрытого эндпоинта.
  3. Testcontainers — один IT на репозиторий.

Дальше

Testcontainers · JWT + Security · Gradle · Spring Boot