Внедрение зависимостей в JUnit 5
JUnit 5 поддерживает внедрение зависимостей (Dependency Injection, DI) в конструкторы и тестовые методы — это упрощает повторное использование данных и делает тесты короче и надёжнее. Используйте встроенные ParameterResolver’ы (например, TestInfo) для доступа к метаданным теста или подключайте собственные расширения через @ExtendWith.

Цель модульного тестирования — максимально быстро обнаруживать ошибки. JUnit 5 предоставляет удобный и эффективный инструмент: внедрение зависимостей (DI). Это помогает избавляться от повторного создания одних и тех же тестовых данных в разных классах и повышает читаемость тестов.
Что такое внедрение зависимостей
В одном предложении: внедрение зависимостей — это шаблон проектирования, при котором объект получает свои внешние зависимости извне, а не создаёт их сам.
Ключевая польза: вы делите ответственность — создание данных и проверка поведения отделены. Это снижает дублирование и делает тесты предсказуемыми.
Краткое определение терминов
- Dependency Injection (DI): механизм передачи зависимостей извне.
- ParameterResolver: интерфейс JUnit 5, который создаёт и передаёт параметры в тест.
- TestInfo: встроенный тип, через который можно получать метаданные текущего теста.
Внедрение зависимостей в JUnit 5
JUnit 5 разрешает параметры в конструкторах тестовых классов и в самих тестовых методах. Это значит, что вам не нужно писать вспомогательные статики или фабрики только для передачи контекста теста.
Особенности:
- Можно передавать сколько угодно параметров — главное, чтобы для каждого имелся доступный ParameterResolver.
- JUnit поставляется с несколькими встроенными резолверами (например, TestInfo, RepetitionInfo и др.).
- Для сторонних типов подключайте свои расширения через @ExtendWith.
Пример: использование TestInfo
Ниже пример, который демонстрирует внедрение TestInfo в конструктор и методы тестового класса. TestInfo предоставляет API для получения имени теста и другой метаинформации.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
class InfoTestInterfaceTest {
// Внедрение объекта TestInfo в конструктор тестового класса
InfoTestInterfaceTest(TestInfo testInfo) {
assertEquals("InfoTestInterfaceTest", testInfo.getDisplayName());
}
// Внедрение объекта TestInfo в метод
@Test
void testMethodName(TestInfo testInfo) {
assertEquals("testMethodName(TestInfo)", testInfo.getDisplayName());
}
@Test
@DisplayName("method using the @DisplayName annotation")
void testMethodNameTwo(TestInfo testInfo) {
assertEquals("method using the @DisplayName annotation", testInfo.getDisplayName());
}
}TestInfo имеет несколько полезных методов; наиболее часто используется getDisplayName(), который возвращает отображаемое имя текущего теста — либо сгенерированное JUnit, либо явно заданное через @DisplayName.
Использование DI в методах подготовки и уборки
Аннотации @BeforeAll, @BeforeEach, @AfterAll и @AfterEach также поддерживают параметры. Передавайте нужные зависимости в параметры этих методов вместо обращения к статическим полям или созданию ресурсов в каждом тесте.
Пример:
@BeforeEach
void setUp(TestInfo testInfo) {
// подготовить окружение с учётом имени теста
}
@AfterEach
void tearDown(TestInfo testInfo) {
// очистка ресурсов
}Важно: @BeforeAll для нестатических методов требует использования расширения @TestInstance(Lifecycle.PER_CLASS) или статического метода при обычном жизненном цикле.
Когда DI в тестах не подходит или ограничен (контрпримеры)
- Когда зависимость имеет глобальное состояние, управлять которым удобнее через специальные фикстуры или контейнеры (например, embedded DB с общим lifecycle).
- Для параметров, которые зависят от рантайма среды (например, системные переменные, реальные API-сервис) иногда проще использовать имитации/моки и фабрики, чем резолверы.
- Если ваш проект использует старые версии JUnit (до 5), DI в тестах недоступен.
Альтернативные подходы
- Фабрики тестовых данных (Test Data Builders): хороший подход для сложных объектов.
- Статические помощники (utility methods): просты, но приводят к дублированию состояния.
- Внешние контейнеры/fixtures (Docker, Testcontainers): когда требуется интеграция с сервисами.
Памятки и эвристики
- Если одинаковые параметры появляются в многих тестах — подключайте ParameterResolver или расширение.
- Начинайте с TestInfo и RepetitionInfo — они встроены и не требуют дополнительной конфигурации.
- Не делайте ParameterResolver слишком «толстым» — пусть он возвращает простые и предсказуемые объекты.
- Избегайте побочных эффектов в резолверах.
Шпаргалка: какие параметры JUnit умеет резолвить из коробки
- TestInfo — метаданные теста (getDisplayName(), getTestMethod() и т.д.)
- RepetitionInfo — информация о повторяющемся тесте
- TestReporter — для логирования дополнительных данных
Если нужно больше — реализуйте ParameterResolver или используйте @ExtendWith с существующими расширениями.
Ролевая чек-лист: кто и что должен проверить
Разработчик
- Проверить, что тесты читаемы и не создают лишнего состояния.
- Перевести повторяющиеся параметры в резолверы или фабрики.
Team Lead / QA
- Убедиться, что внедрение зависимостей не скрывает важных побочных эффектов.
- Проверить, что lifecycle (BeforeAll/AfterAll) корректен для каждого класса.
Инженер по инфраструктуре
- Оценить необходимость использовать глобальные ресурсы (интеграционные контейнеры) вместо резолверов.
Маленькая методология внедрения DI в тестовый проект (шаги)
- Найдите дублирование: одинаковые данные/настройки в нескольких тестах.
- Выделите данные в ParameterResolver или отдельную фабрику.
- Покрытие: добавьте тесты для нового резолвера (проверка предсказуемости и отсутствия побочных эффектов).
- Документируйте и снабдите примерами использования в репозитории.
- Рефакторите тесты поэтапно, чтобы не ломать CI.
Пример простого собственного ParameterResolver (схема)
- Реализуйте интерфейс ParameterResolver.
- В методе supportsParameter возвращайте true для целевого типа.
- В resolveParameter создавайте/возвращайте объект.
- Зарегистрируйте резолвер через @ExtendWith(YourResolver.class).
public class MyResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
return pc.getParameter().getType().equals(MyType.class);
}
@Override
public Object resolveParameter(ParameterContext pc, ExtensionContext ec) {
return new MyType(/*...*/);
}
}Критерии приёмки
- Тесты используют DI для общих параметров, дублирование кода уменьшено.
- Резолверы не имеют скрытых побочных эффектов.
- CI выполняет тесты стабильно: нет межтестовых зависимостей.
Однострочные определения
- ParameterResolver: механизм создания и передачи параметров в тесты.
- TestInfo: объект с метаданными текущего теста.
Decision flowchart
flowchart TD
A[Есть дублирование тестовых данных?] -->|Да| B{Параметры простые?}
B -->|Да| C[Создать ParameterResolver]
B -->|Нет| D[Использовать фабрики/Builders или Testcontainers]
A -->|Нет| E[Оставить текущую реализацию]
C --> F[Зарегистрировать через @ExtendWith]
D --> F
F --> G[Рефакторинг тестов]Резюме
Внедрение зависимостей в JUnit 5 делает тестовый код проще, уменьшает дублирование и улучшает поддержку. Начните с встроенных резолверов (TestInfo, RepetitionInfo), затем при необходимости добавьте свои ParameterResolver’ы и используйте @ExtendWith для регистрации. Внедряйте DI итеративно, проверяя отсутствие побочных эффектов и стабильность CI.
Короткие советы
- Используйте DI для передачи контекста и метаданных.
- Для сложных внешних ресурсов смотрите в сторону контейнеров и фабрик.
- Пишите тесты для собственных резолверов.
Похожие материалы
Восстановить удалённые закладки в Edge
Клонирование приложений на Samsung через Dual Messenger
TypeORM + NestJS: быстрая интеграция
Отключить автоматические обновления Windows 11