Гид по технологиям

Внедрение зависимостей (DI) в JUnit 5

5 min read Testing Обновлено 29 Mar 2026
DI в JUnit 5: внедрение зависимостей в тестах
DI в JUnit 5: внедрение зависимостей в тестах

Ноутбук с кодом и двумя руками на клавиатуре

Цель модульного теста — найти ошибки в приложении как можно раньше. Хотя существует несколько способов добиться этого, стоит выбирать наиболее эффективный.

В наборе тестов JUnit может быть несколько классов, которым нужны одинаковые данные, но повторно использовать тестовые данные бывает неудобно. Раньше обычным решением было написать вспомогательный метод и вызывать его из каждого тестового класса. JUnit 5 предлагает более удобный подход: внедрение зависимостей (DI).

Что такое внедрение зависимостей?

Внедрение зависимостей (Dependency Injection, DI) — это шаблон проектирования, при котором один объект предоставляет зависимости другому. Проще: если класс A зависит от объекта B для своей работы, DI позволяет предоставить B извне, а не создавать B внутри A.

Короткое определение терминов:

  • DI: предоставление внешних зависимостей классу вместо их создания внутри класса.
  • ParameterResolver: компонент JUnit, который умеет подставлять параметры в методы и конструкторы тестов.

DI в JUnit 5 — что изменилось

JUnit 5 разрешает параметризовать тестовые методы и конструкторы. Это важно, потому что предыдущие версии не допускали параметров в тестовых методах и конструкторах.

  • Можно передавать сколько угодно параметров, но каждый параметр должен уметь разрешать ParameterResolver.
  • Встроенные резолверы JUnit автоматически обрабатывают несколько типов (например, TestInfo). Для других типов нужно регистрировать расширения через @ExtendWith.

Пример: Injection с TestInfo

Ниже пример использует встроенный TestInfoParameterResolver — JUnit сам подставляет объект, реализующий интерфейс TestInfo, в конструктор и методы тестового класса.

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 {  
    // Injecting a testInfo object into the InfoTestInterfaceTest constructor  
    InfoTestInterfaceTest(TestInfo testInfo) {  
        assertEquals("InfoTestInterfaceTest", testInfo.getDisplayName());  
    }  
  
    // Injecting a testInfo object into methods  
    @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());  
    }  
}  

Код выше показывает: JUnit подставляет TestInfo в конструктор и методы. Метод getDisplayName() возвращает отображаемое имя текущего теста — по умолчанию оно выводится на основе класса и имени метода, но если вы используете аннотацию @DisplayName, будет возвращён текст из этой аннотации.

Отчёт JUnit о внедрении зависимостей

Внедрение в методы @BeforeEach, @AfterEach, @BeforeAll и @AfterAll

JUnit 5 также позволяет передавать зависимости в методы с аннотациями жизненного цикла: @BeforeAll, @BeforeEach, @AfterEach, @AfterAll. Достаточно добавить параметр в сигнатуру метода — и, если есть подходящий резолвер, JUnit подставит объект.

Важно: для @BeforeAll метод обычно должен быть статическим, если не используется @TestInstance(Lifecycle.PER_CLASS). При использовании PER_CLASS можно применять нестатические @BeforeAll и получать внедрённые параметры.

Почему использовать DI в тестах

  • Уменьшает дублирование кода и конфигураций в разных классах тестов.
  • Улучшает читаемость: зависимости представлены явно в подписи метода/конструктора.
  • Упрощает настройку тестовой среды и переиспользование фикстур.

Когда DI не подходит

  • Простые юнит-тесты, где зависимость тривиальна и нет повторного использования.
  • Тесты для статических утилит, где DI не даёт преимуществ.
  • Когда нужно строго контролировать время создания объекта (например, тестирование логики конструктора с побочными эффектами).

Альтернативные подходы

  • Вспомогательные фабричные методы/утилиты: хороши для простых случаев.
  • Аннотация @TestInstance(Lifecycle.PER_CLASS) для хранения состояния между методами без глобальных статиков.
  • Параметризованные тесты (ParameterizedTest) для набора входных данных.
  • Мокирование через Mockito/MockK для управления поведением зависимостей.
  • Написание собственного ParameterResolver и регистрация через @ExtendWith для специфичных типов.

Быстрая методика внедрения DI в существующий набор тестов

  1. Определите повторяющиеся объекты/фикстуры, которые создаются в нескольких классах.
  2. Проверьте, есть ли встроенный резолвер (например, TestInfo). Если нет — решите, писать собственный или использовать расширение.
  3. Для нестатических жизненных методов рассмотрите @TestInstance(Lifecycle.PER_CLASS).
  4. Замените создание объектов на передачу их в конструктор/метод теста.
  5. Добавьте тесты на совместимость: сборка, запуск в IDE и CI.

Чек‑лист для ревью тестов

  • Тесты явно принимают зависимости в сигнатуре метода/конструктора, а не создают их тайно.
  • Нет дублирования кода для создания одной и той же фикстуры.
  • Использование DI не скрывает побочных эффектов или общего состояния между тестами.
  • Документированы нестандартные резолверы (через @ExtendWith).

Критерии приёмки

  • Тесты компилируются и проходят в локальной среде и в CI.
  • Снижение дублирования конфигурации/данных между тестовыми классами.
  • Отсутствие флейковых тестов из‑за неявного состояния.

Короткий глоссарий

  • DI: внедрение зависимостей, передача внешних объектов в класс/метод.
  • ParameterResolver: интерфейс JUnit для подстановки параметров.
  • TestInfo: интерфейс JUnit с информацией о текущем тесте (например, отображаемое имя).
  • @ExtendWith: аннотация для подключения расширений/резолверов.

Decision tree — стоит ли использовать DI?

flowchart TD
  A[Нужно ли переиспользовать фикстуру в нескольких тестах?] -->|Да| B[Есть встроенный резолвер?]
  A -->|Нет| C[Оставьте фабрику/утилиту]
  B -->|Да| D[Использовать DI в сигнатурах тестов]
  B -->|Нет| E[Рассмотреть @ExtendWith или фабрики]
  E --> F{Нужен ли специфичный резолвер?}
  F -->|Да| G[Написать ParameterResolver и подключить]
  F -->|Нет| H[Использовать утилиту/мок]

Примеры ошибок и когда DI может подвести

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

Роль‑бейсд чек‑лист

  • Автор теста:
    • Явно указал зависимости в сигнатуре.
    • Обеспечил чистоту состояния между тестами.
    • Добавил примечание, если подключил нестандартный резолвер.
  • Рецензент:
    • Проверил отсутствие дублирования.
    • Проверил, что внедрение не скрывает логику теста.
    • Убедился, что тесты проходят в CI.

Важно: внедрение зависимостей улучшает структуру тестов, но требует дисциплины по управлению состоянием и ясной документации расширений.

Итог

Внедрение зависимостей в JUnit 5 — мощный инструмент для уменьшения дублирования, явного указания фикстур и повышения читаемости тестов. Используйте встроенные резолверы, подключайте свои через @ExtendWith при необходимости и следите за чистотой состояния между тестами.

Ключевые выводы:

  • JUnit 5 поддерживает DI в конструкторах и методах тестов.
  • TestInfo — удобный встроенный пример параметра.
  • DI уменьшает дублирование, но требует контроля состояния и документации расширений.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Фон таблицы в Microsoft Word: шаги и советы
Советы

Фон таблицы в Microsoft Word: шаги и советы

Редактирование ночного неба в Lightroom Mobile
Фотография

Редактирование ночного неба в Lightroom Mobile

Скачивание контента BBC iPlayer для офлайн
Видео

Скачивание контента BBC iPlayer для офлайн

Эффективное расписание для учёбы
Учёба

Эффективное расписание для учёбы

Как обойти интернет‑цензуру: DNS, VPN и Tor
Интернет-безопасность

Как обойти интернет‑цензуру: DNS, VPN и Tor

Upwork Desktop: учёт времени и скриншоты
Фриланс

Upwork Desktop: учёт времени и скриншоты