JUnit5
Модуль предоставляет расширение для JUnit 5, которое позволяет тестировать приложение через тот же граф компонентов, что используется во время работы приложения.
Расширение Kora для JUnit 5 предназначено для компонентного и интеграционного тестирования исходного кода, который впоследствии будет работать в реальном приложении.
В тесте используется контейнер зависимостей основного приложения: его можно ограничить нужными компонентами,
расширить тестовыми компонентами или заменить отдельные части заглушками.
Модуль позволяет проводить:
Компонентные тесты— тестирование одного компонента.Межкомпонентные тесты— тестирование нескольких компонентов и их взаимодействия друг с другом.Интеграционные тесты— тестирование компонентов и взаимодействия с внешними системами.
Рекомендуется дополнительно тестировать артефакт сервиса, упакованный в итоговый образ, как черный ящик с помощью библиотеки Testcontainers.
Пошаговый разбор перед справочным описанием смотрите в разделах Компонентное тестирование, Интеграционное тестирование и Тестирование черного ящика.
Подключение¶
Зависимость build.gradle:
Настройка платформы JUnit build.gradle:
Зависимость build.gradle.kts:
Настройка платформы JUnit build.gradle.kts:
Использование¶
Примеры будут показаны применительно к такому приложению:
Тест¶
Чтобы включить расширение Kora, пометьте тестовый класс аннотацией @KoraAppTest.
Аннотация подключает расширение JUnit 5, находит сгенерированный граф указанного приложения @KoraApp и подготавливает контейнер зависимостей для теста.
Параметры аннотации @KoraAppTest:
value— класс, помеченный@KoraApp, граф компонентов которого будет использоваться в тесте (обязательный, без значения по умолчанию).components— дополнительные классы компонентов, которые должны быть включены в тестовый граф в дополнение к компонентам, найденным через@TestComponent(по умолчанию:{}).modules— дополнительные модули с фабричными методами компонентов, которые должны быть подключены к тестовому графу (по умолчанию:{}).
В modules можно указывать только интерфейсы модулей. Если требуется протестировать весь граф, внедрите KoraAppGraph или не ограничивайте граф отдельными компонентами @TestComponent.
Компонент¶
Для внедрения и выбора компонентов для тестирования используйте аннотацию @TestComponent.
Она позволяет внедрять компоненты в аргументы тестовых методов, в конструктор и/или в поля тестового класса, а также ограничивает контейнер зависимостей этими компонентами.
Все компоненты, перечисленные в полях теста и/или в аргументах метода/конструктора и помеченные @TestComponent, будут внедрены как зависимости в рамках теста.
Тестовый контейнер зависимостей будет ограничен этими компонентами и их зависимостями.
Важно, что компоненты внутри теста должны использоваться хотя бы одним @Root компонентом, который также указан в рамках теста.
Пример теста, где компоненты внедряются в поля:
Пример теста, где компоненты внедряются в конструктор:
Пример теста, где компоненты внедряются в аргументы метода:
Правила внедрения¶
Компоненты можно внедрять тремя способами: в поле тестового класса, в конструктор или в параметр тестового метода. Выбранная форма влияет на то, когда расширение Kora может получить доступ к экземпляру тестового класса и какие дополнительные механизмы доступны.
- Поля подходят для большинства тестов и совместимы с
KoraAppTestConfigModifier,KoraAppTestGraphModifier,PER_METHODиPER_CLASS. - Внедрение через конструктор удобно для неизменяемых полей, но несовместимо с
KoraAppTestConfigModifierиKoraAppTestGraphModifier, поскольку расширению нужен экземпляр тестового класса для вызоваconfig()илиgraph(), тогда как этот экземпляр еще создается во время внедрения через конструктор. - Параметры метода удобны для зависимостей, локальных для конкретного теста; в режиме
PER_METHODграф включает параметры текущего метода, а в режимеPER_CLASSрасширение заранее собирает параметры@TestComponentиз всех методов класса. - Если используется внедрение через конструктор,
@TestComponent,@Mock,@Spy,@MockKили@SpyKнельзя также внедрять в параметры тестового метода. - В режиме
PER_CLASS@Mock/@MockKнельзя внедрять в параметры тестового метода, поскольку заглушки уровня метода живут меньше, чем общий граф тестового класса. - Один и тот же элемент нельзя одновременно объявить как обычный
@TestComponent, заглушку (mock) и шпион (spy): расширение завершит тест ошибкой конфигурации.
Если тесту нужны KoraAppTestConfigModifier или KoraAppTestGraphModifier, используйте внедрение через поля или параметры метода.
Если требуется внедрение через конструктор, лучше вынести изменение конфигурации и графа в отдельное тестовое @KoraApp или подключаемый модуль.
Тег¶
Чтобы внедрить зависимость/заглушку, помеченную @Tag, необходимо указать соответствующую аннотацию @Tag рядом с аргументом для внедрения:
Граф приложения¶
Если тесту нужен прямой доступ к подготовленному графу, внедрите KoraAppGraph в поле, конструктор или аргумент тестового метода.
Он может получить один или несколько компонентов по типу, а также учитывать @Tag.
Основные методы KoraAppGraph:
getFirst(Type type)/getFirst(Class<T> type)— возвращают первый найденный компонент илиnull.getFirst(Type type, Class<?>... tags)/getFirst(Class<T> type, Class<?>... tags)— возвращают первый компонент с указанными тегами илиnull.findFirst(...)— возвращаетOptional<T>вместоnull.getAll(...)— возвращает все компоненты указанного типа, при необходимости учитывая теги.
KoraAppGraph нельзя использовать как цель для @Mock, @Spy, @MockK или @SpyK, поскольку это служебный объект тестового расширения, а не компонент приложения.
Заглушка¶
Для создания заглушки компонента в Java в рамках теста предлагается использовать аннотации из библиотеки Mockito совместно с аннотацией @TestComponent.
Требуется добавить библиотеку Mockito как зависимость в build.gradle:
Важно, предполагается, что MockitoExtension использоваться не будет и будет отключено, его нельзя совмещать вместе с @KoraAppTest.
Поддерживаются аннотации @Mock и @Spy, а также все параметры этих аннотаций. Рекомендуется подробнее ознакомиться с тем, как работают эти аннотации, в официальной документации библиотеки Mockito.
Аннотация @Mock позволяет сделать заглушку класса
помеченного компонента и управлять поведением его методов с помощью Mockito, либо методы будут возвращать значения по умолчанию: void, значения по умолчанию для примитивов, пустые коллекции и null для всех остальных объектов.
Компонент-заглушка будет внедрен как зависимость в аргументы и/или поля тестового класса и во все компоненты, которым он требовался как зависимость. Все зависимые компоненты, которые больше нигде в рамках теста не требуются, будут исключены как ненужные.
Пример теста с использованием компонента @Mock и внедрением заглушки в поле:
@KoraAppTest(Application.class)
class SomeTests {
@Mock
@TestComponent
private Supplier<String> component1;
@BeforeEach
void mock() {
Mockito.when(component1.get()).thenReturn("?");
}
@Test
void example() {
assertEquals("?", component1.get());
}
}
Аннотация @Spy позволяет сделать шпион-фасад реализации класса компонента из контейнера зависимостей, который по умолчанию будет иметь исходное поведение методов компонента, но, как и в случае с заглушками, их поведение можно переопределить.
Компонент-шпион будет внедрен как зависимость в аргументы и/или поля тестового класса и во все компоненты, которым он требовался как зависимость.
Пример теста с использованием компонента @Spy и внедрением шпиона в аргумент метода:
@KoraAppTest(Application.class)
class SomeTests {
@Test
void example(@Spy @TestComponent Supplier<String> component1) {
Mockito.when(component1.get()).thenReturn("?");
assertEquals("?", component1.get());
}
}
Также можно сделать шпион из значения поля тестового класса.
Компонент-шпион будет внедрен как зависимость в аргументы и/или поля тестового класса и во все компоненты, которым он требовался как зависимость. Все зависимые компоненты, которые больше нигде в рамках теста не требуются, будут исключены как ненужные.
Пример теста с использованием компонента-шпиона @Spy:
Для создания заглушек компонентов в Kotlin предлагается использовать аннотации из библиотеки MockK совместно с аннотацией @TestComponent.
Требуется подключить библиотеку MockK как зависимость в build.gradle.kts:
Важно, предполагается, что MockkExtension использоваться не будет и будет отключено, его нельзя совмещать вместе с @KoraAppTest.
Поддерживаются аннотации @MockK и @SpyK, а также все параметры этих аннотаций.
При желании также можно использовать Mockito. Для более подробного описания того, как работают Kora и Mockito, следует прочитать вкладку Java этого раздела. Для улучшения взаимодействия между Mockito и Kotlin можно использовать библиотеку Mockito Kotlin.
Важно, предполагается, что MockitoExtension использоваться не будет и будет отключено, его нельзя совмещать вместе с @KoraAppTest.
Аннотация @MockK позволяет сделать заглушку класса
помеченного компонента и управлять поведением его методов с помощью MockK.
Компонент-заглушка будет внедрен как зависимость в аргументы и/или поля тестового класса и во все компоненты, которым он требовался как зависимость. Все зависимые компоненты, которые больше нигде в рамках теста не требуются, будут исключены как ненужные.
Пример теста с использованием компонента @MockK и внедрением заглушки:
@KoraAppTest(Application::class)
class SomeTests(@MockK @TestComponent val component1: Supplier<String>) {
@BeforeEach
fun mock() {
every { component1.get() } returns "?"
}
@Test
fun example() {
assertEquals("?", component1.get())
}
}
Аннотация @SpyK позволяет сделать шпион-фасад реализации класса компонента из контейнера зависимостей, который по умолчанию будет иметь исходное поведение методов компонента, но, как и в случае с заглушками, их поведение можно переопределить.
Компонент-шпион будет внедрен как зависимость в аргументы и/или поля тестового класса и во все компоненты, которым он требовался как зависимость.
Пример теста с использованием компонента @SpyK и встраиванием шпиона в аргумент метода:
@KoraAppTest(Application::class)
class SomeTests {
@Test
fun example(@SpyK @TestComponent component1: Supplier<String>) {
every { component1.get() } returns "?"
assertEquals("?", component1.get())
}
}
Также можно сделать шпион из значения поля тестового класса.
Компонент-шпион будет внедрен как зависимость в аргументы и/или поля тестового класса и во все компоненты, которым он требовался как зависимость. Все зависимые компоненты, которые больше нигде в рамках теста не требуются, будут исключены как ненужные.
Пример теста с использованием компонента-шпиона @SpyK:
Строгость заглушек¶
Заглушки Mockito можно проверять с помощью аннотации @MockitoStrictness.
Она задает уровень проверки для заглушек Mockito, созданных расширением Kora в рамках тестового класса.
Расширение ведет себя аналогично MockitoSession: после завершения теста оно передает созданные заглушки на проверку Mockito и сообщает о неиспользованных или подозрительных настройках поведения.
Если @MockitoStrictness не указана, Kora использует Strictness.WARN: тест не падает, но в лог записываются предупреждения.
Поддерживаемые уровни:
Strictness.WARN— значение по умолчанию; записывает предупреждения в лог и не приводит к падению теста.Strictness.STRICT_STUBS— строгий режим; неиспользованная настройка поведения приводит к падению теста, например сUnnecessaryStubbingException.Strictness.LENIENT— мягкий режим; отключает проверки неиспользованных настроек поведения.
Если у конкретной @Mock есть собственный параметр strictness, он применяется к настройкам этой заглушки.
@MockitoStrictness удобна как общий уровень для всего тестового класса, чтобы не дублировать настройку на каждой заглушке.
В примере выше Mockito.when(component1.get()).thenReturn("?") должно быть использовано тестом.
Если убрать вызов component1.get() из тестового метода, Strictness.STRICT_STUBS приведет к падению теста.
Для Kotlin с Mockito Kotlin действует тот же механизм, поскольку проверку выполняет Mockito.
@MockitoStrictness не применяется к заглушкам MockK.
Тестовый граф¶
Иногда в рамках тестов может потребоваться использовать расширенный контейнер зависимостей. Например, тестовое приложение может расширять основное приложение и добавлять компоненты, которые нужны только в тестах.
Такой подход полезен, когда у вас есть разные приложения Read API и Write API с общими компонентами, которые могут потребоваться при тестировании одного и другого. Или же вам могут понадобиться какие-то функции сохранения/удаления/обновления исключительно для тестирования в качестве быстрой тестовой утилиты.
Рекомендация
Настоятельно рекомендуется тестировать приложения как черный ящик и полагаться на этот подход как на основной источник истины и корректности приложения.
Приложение может работать по-разному в зависимости от флагов JVM, базового образа и нативных библиотек, различий между частичными и полными конфигурациями, различий в преобразовании на точках входа приложения, использования реестров схем и так далее. Только prod-ready образ может гарантировать максимально близкое к реальному окружение тестирования.
Представим, что приложение выглядит так:
В тестах можно создать отдельное тестовое @KoraApp, которое расширяет основное приложение, и использовать этот граф.
Для этого сценария требуется сгенерированный субмодуль основного приложения: без него тестовое приложение не сможет унаследовать и подключить компоненты основного графа.
Сначала включите параметр, который создает субмодуль основного приложения, в build.gradle:
Затем требуется создать расширенный тестовый граф приложения в каталоге тестовых исходников.
Не забудьте пометить компоненты как @Root, поскольку они, скорее всего, никем не используются,
кроме тестов, и иначе не будут включены в граф:
Чтобы граф тестового приложения был сгенерирован, нужно добавить обработчики как тестовые зависимости в build.gradle:
Может потребоваться исключить сканирование сгенерированных Kora классов средствами JUnit (иногда возникает ошибка при поиске тестов):
Теперь вы можете использовать расширенный граф приложения в своих тестах:
Если наследование от основного @KoraApp не требуется и нужно добавить только фабричные методы из отдельного модуля,
используйте параметр modules аннотации @KoraAppTest.
modules принимает интерфейсы модулей, а не классы компонентов:
Итого:
kora.app.submodule.enabled=trueнужен, когда тестовое@KoraAppрасширяет основное@KoraApp.@KoraAppTest(modules = ...)подходит для случаев, когда к тестовому графу нужно просто подключить дополнительные модули.- Компоненты, которые должны появиться в ограниченном тестовом графе, все равно должны быть достижимы из
@TestComponent,componentsилиKoraAppGraph.
Конфигурация теста¶
По умолчанию будет использоваться базовая конфигурация, как и в случае запуска реального приложения.
Чтобы изменить или добавить конфигурацию в рамках тестов, тестовый класс должен реализовать KoraAppTestConfigModifier,
а метод config() должен возвращать KoraConfigModification.
KoraAppTestConfigModifier нельзя использовать вместе с внедрением компонентов в конструктор тестового класса:
расширению нужно получить изменение конфигурации до создания тестового графа, а для этого экземпляр теста должен уже существовать.
Переменные окружения¶
Если тесту нужно использовать конфигурацию по умолчанию, которая использовалась бы при запуске приложения,
и требуется лишь подставить переменные окружения, можно воспользоваться механизмом SystemProperty в KoraConfigModification:
Предположим, есть такая конфигурация application.conf:
Чтобы использовать такой конфиг и передать только переменные окружения, нужно вернуть такой KoraConfigModification:
@KoraAppTest(Application.class)
class SomeTests implements KoraAppTestConfigModifier {
@NotNull
@Override
public KoraConfigModification config() {
return KoraConfigModification
.ofSystemProperty("POSTGRES_JDBC_URL", "jdbc:postgresql://localhost:5432/postgres")
.withSystemProperty("POSTGRES_USER", "postgres")
.withSystemProperty("POSTGRES_PASS", "postgres");
}
}
@KoraAppTest(Application::class)
class SomeTests : KoraAppTestConfigModifier {
override fun config(): KoraConfigModification {
return KoraConfigModification
.ofSystemProperty("POSTGRES_JDBC_URL", "jdbc:postgresql://localhost:5432/postgres")
.withSystemProperty("POSTGRES_USER", "postgres")
.withSystemProperty("POSTGRES_PASS", "postgres")
}
}
Если нужно передать сразу несколько значений, используйте withSystemProperties(Map<String, String>):
@KoraAppTest(Application.class)
class SomeTests implements KoraAppTestConfigModifier {
@NotNull
@Override
public KoraConfigModification config() {
return KoraConfigModification
.ofSystemProperty("POSTGRES_JDBC_URL", "jdbc:postgresql://localhost:5432/postgres")
.withSystemProperties(Map.of(
"POSTGRES_USER", "postgres",
"POSTGRES_PASS", "postgres"
));
}
}
@KoraAppTest(Application::class)
class SomeTests : KoraAppTestConfigModifier {
override fun config(): KoraConfigModification {
return KoraConfigModification
.ofSystemProperty("POSTGRES_JDBC_URL", "jdbc:postgresql://localhost:5432/postgres")
.withSystemProperties(
mapOf(
"POSTGRES_USER" to "postgres",
"POSTGRES_PASS" to "postgres"
)
)
}
}
Файл конфигурации¶
Пример предоставления конфигурации в виде файла:
Текст конфигурации¶
Пример добавления конфигурации в виде строки выглядел бы так, в этом случае будет использоваться только эта конфигурация без каких-либо файлов конфигурации:
Подстановка в конфигурации¶
Подстановка переменных окружения, показанная в разделе Переменные окружения, также работает со встроенной конфигурацией:
объявите плейсхолдеры ${ENV} прямо внутри конфигурации ofString(...) и разрешите их через цепочку вызовов withSystemProperty(...).
Это удобно, когда вся конфигурация описана в тесте, но некоторые значения (порты, хосты, учетные данные) известны только во время выполнения:
@KoraAppTest(Application.class)
class SomeTests implements KoraAppTestConfigModifier {
@Override
public @Nonnull KoraConfigModification config() {
return KoraConfigModification.ofString("""
myconfig {
myinnerconfig {
first = ${ENV_FIRST}
second = ${ENV_SECOND}
}
}
""")
.withSystemProperty("ENV_FIRST", "1")
.withSystemProperty("ENV_SECOND", "2");
}
}
@KoraAppTest(Application::class)
class SomeTests : KoraAppTestConfigModifier {
override fun config(): KoraConfigModification {
return KoraConfigModification.ofString(
"""
myconfig {
myinnerconfig {
first = \${ENV_FIRST}
second = \${ENV_SECOND}
}
}
""".trimIndent()
)
.withSystemProperty("ENV_FIRST", "1")
.withSystemProperty("ENV_SECOND", "2")
}
}
Testcontainers¶
Распространенное применение KoraAppTestConfigModifier — интеграция с Testcontainers:
тест запускает контейнер и передает его значения подключения времени выполнения в конфигурацию через config().
Testcontainers назначает случайный порт хоста при каждом запуске, поэтому значения нельзя жестко зашивать — они объявляются как плейсхолдеры ${...} во встроенной конфигурации
и заполняются из геттеров контейнера через withSystemProperty(...).
Поскольку config() выполняется до построения тестового графа, конфигурация готова до создания любого компонента.
По той же причине KoraAppTestConfigModifier несовместим с внедрением через конструктор: используйте внедрение через поле или параметр метода, как показано ниже.
Добавьте зависимости Testcontainers в build.gradle:
testImplementation "org.testcontainers:junit-jupiter:1.21.4"
testImplementation "org.testcontainers:postgresql:1.21.4"
@Testcontainers
@KoraAppTest(Application.class)
class SomeIntegrationTests implements KoraAppTestConfigModifier {
@Container
private static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:16");
@TestComponent
private SomeService service;
@NotNull
@Override
public KoraConfigModification config() {
return KoraConfigModification.ofString("""
db {
jdbcUrl = ${POSTGRES_JDBC_URL}
username = ${POSTGRES_USER}
password = ${POSTGRES_PASS}
poolName = "kora"
}
""")
.withSystemProperty("POSTGRES_JDBC_URL", POSTGRES.getJdbcUrl())
.withSystemProperty("POSTGRES_USER", POSTGRES.getUsername())
.withSystemProperty("POSTGRES_PASS", POSTGRES.getPassword());
}
@Test
void example() {
// interact with the service backed by the container
}
}
Добавьте зависимости Testcontainers в build.gradle.kts:
testImplementation("org.testcontainers:junit-jupiter:1.21.4")
testImplementation("org.testcontainers:postgresql:1.21.4")
@Testcontainers
@KoraAppTest(Application::class)
class SomeIntegrationTests : KoraAppTestConfigModifier {
companion object {
@Container
@JvmStatic
val POSTGRES = PostgreSQLContainer("postgres:16")
}
@TestComponent
lateinit var service: SomeService
override fun config(): KoraConfigModification {
return KoraConfigModification.ofString(
"""
db {
jdbcUrl = \${POSTGRES_JDBC_URL}
username = \${POSTGRES_USER}
password = \${POSTGRES_PASS}
poolName = "kora"
}
""".trimIndent()
)
.withSystemProperty("POSTGRES_JDBC_URL", POSTGRES.jdbcUrl)
.withSystemProperty("POSTGRES_USER", POSTGRES.username)
.withSystemProperty("POSTGRES_PASS", POSTGRES.password)
}
@Test
fun example() {
// interact with the service backed by the container
}
}
Полный разбор — зависимости, тестовое @KoraApp, миграции и настройка репозитория — смотрите в руководстве Интеграционное тестирование.
Изменение контейнера¶
Чтобы добавить, заменить или программно создать заглушки в контейнере приложения без аннотаций, реализуйте KoraAppTestGraphModifier
и верните KoraGraphModification из метода graph().
KoraAppTestGraphModifier нельзя использовать вместе с внедрением компонентов в конструктор тестового класса:
расширению нужно получить изменение графа до создания графа и внедрения компонентов.
KoraGraphModification поддерживает следующие операции:
addComponent(...)— добавляет новый компонент в тестовый граф.replaceComponent(...)— заменяет существующий компонент, при этом его зависимости остаются в графе.mockComponent(...)— заменяет существующий компонент заглушкой и удаляет реальные зависимости заменяемого компонента из графа, если они больше не нужны тесту.
addComponent(...) и replaceComponent(...) имеют перегрузки с Function<KoraAppGraph, T>, если новый компонент должен быть построен из уже инициализированных компонентов графа.
Для компонентов с @Tag используйте перегрузки с List<Class<?>> tags.
Добавление¶
Пример добавления компонента в граф:
@KoraAppTest(value = Application.class)
class SomeTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.addComponent(TypeRef.of(Supplier.class, Integer.class), () -> (Supplier<Integer>) () -> 1);
}
@Test
void example(@TestComponent Supplier<Integer> supplier) {
assertEquals(1, supplier.get());
}
}
@KoraAppTest(value = Application::class)
class SomeTests : KoraAppTestGraphModifier {
override fun graph(): KoraGraphModification {
return KoraGraphModification.create()
.addComponent(TypeRef.of(Supplier::class.java, Int::class.java), Supplier { Supplier { 1 } })
}
@Test
fun example(@TestComponent supplier: Supplier<Int>) {
assertEquals(1, supplier.get())
}
}
В случае, когда требуется добавить компоненты с использованием реального компонента из графа, это также доступно через другую сигнатуру метода:
@KoraAppTest(value = Application.class)
class SomeTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.addComponent(TypeRef.of(Supplier.class, String.class),
(graph) -> {
final Supplier<Integer> existingComponent = (Supplier<Integer>) graph.getFirst(TypeRef.of(Supplier.class, Integer.class));
return (Supplier<String>) () -> "1" + existingComponent.get();
});
}
@Test
void example(@TestComponent Supplier<String> supplier) {
assertEquals(1, supplier.get());
}
}
@KoraAppTest(value = Application::class)
class SomeTests : KoraAppTestGraphModifier {
@Nonnull
override fun graph(): KoraGraphModification {
return KoraGraphModification.create()
.addComponent(TypeRef.of(Supplier::class.java, String::class.java))
{ graph ->
val existingComponent = graph.getFirst(TypeRef.of(Supplier::class.java, Int::class.java))
as Supplier<Int>
Supplier { "1" + existingComponent.get() }
}
}
@Test
fun example(@TestComponent supplier: Supplier<String>) {
assertEquals(1, supplier.get())
}
}
Замена¶
Пример замены компонента в контейнере зависимостей, этот механизм также можно использовать для создания собственных заглушек:
@KoraAppTest(value = Application.class)
class SomeTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.replaceComponent(TypeRef.of(Supplier.class, String.class), List.of(Supplier.class), () -> (Supplier<String>) () -> "?");
}
@Test
void example(@Tag(Supplier.class) @TestComponent Supplier<String> supplier) {
assertEquals("?", supplier.get());
}
}
@KoraAppTest(value = Application::class)
class SomeTests : KoraAppTestGraphModifier {
override fun graph(): KoraGraphModification {
return KoraGraphModification.create()
.replaceComponent(TypeRef.of(Supplier::class.java, String::class.java), listOf(Supplier::class.java), Supplier { Supplier { "?" } })
}
@Test
fun example(@Tag(Supplier::class) @TestComponent supplier: Supplier<String>) {
assertEquals("?", supplier.get())
}
}
В случае, когда требуется заменить компоненты с использованием реального компонента из графа, это также доступно через другую сигнатуру метода:
@KoraAppTest(value = Application.class)
class SomeTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.replaceComponent(TypeRef.of(Supplier.class, Integer.class),
(graph) -> {
final Supplier<Integer> existingComponent = (Supplier<Integer>) graph.getFirst(TypeRef.of(Supplier.class, Integer.class));
return (Supplier<Integer>) () -> 1 + existingComponent.get();
});
}
@Test
void example(@TestComponent Supplier<Integer> supplier) {
assertEquals(1, supplier.get());
}
}
@KoraAppTest(value = Application::class)
class SomeTests : KoraAppTestGraphModifier {
@Nonnull
override fun graph(): KoraGraphModification {
return KoraGraphModification.create()
.replaceComponent(TypeRef.of(Supplier::class.java, Int::class.java))
{ graph ->
val existingComponent = graph.getFirst(TypeRef.of(Supplier::class.java, Int::class.java))
as Supplier<Int>
Supplier { 1 + existingComponent.get() }
}
}
@Test
fun example(@TestComponent supplier: Supplier<Int>) {
assertEquals(1, supplier.get())
}
}
Программная заглушка¶
Если компонент нужно заменить именно как заглушку, используйте mockComponent(...).
В отличие от replaceComponent(...), этот метод сообщает расширению, что реальные зависимости заменяемого компонента не нужны и могут быть исключены из тестового графа.
@KoraAppTest(value = Application.class)
class SomeTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.mockComponent(TypeRef.of(Supplier.class, String.class), () -> Mockito.mock(Supplier.class));
}
@Test
void example(@TestComponent Supplier<String> supplier) {
Mockito.when(supplier.get()).thenReturn("?");
assertEquals("?", supplier.get());
}
}
@KoraAppTest(value = Application::class)
class SomeTests : KoraAppTestGraphModifier {
override fun graph(): KoraGraphModification {
return KoraGraphModification.create()
.mockComponent(TypeRef.of(Supplier::class.java, String::class.java), Supplier { mockk<Supplier<String>>() })
}
@Test
fun example(@TestComponent supplier: Supplier<String>) {
every { supplier.get() } returns "?"
assertEquals("?", supplier.get())
}
}
Инициализация¶
По умолчанию JUnit 5 использует TestInstance.Lifecycle.PER_METHOD, поэтому Kora создает и очищает тестовый граф для каждого тестового метода.
Если контейнер должен инициализироваться один раз для всего тестового класса, пометьте тестовый класс аннотацией @TestInstance(TestInstance.Lifecycle.PER_CLASS):
С PER_CLASS один экземпляр графа используется всеми тестовыми методами класса, а очистка выполняется после завершения всего класса.
Это ускоряет тяжелые интеграционные тесты, но с изменяемым состоянием компонентов и заглушек нужно обращаться аккуратнее.
Ограничения жизненного цикла:
- Когда компоненты внедряются в конструктор,
@TestComponentили заглушки нельзя также внедрять в параметры тестового метода. - Когда компоненты внедряются в конструктор, нельзя использовать
KoraAppTestConfigModifierиKoraAppTestGraphModifier. - В режиме
PER_CLASS@Mock/@MockKнельзя внедрять в параметры тестового метода; используйте поля или конструктор. - Для классов
@Nestedнельзя использовать внедрение в поля внутреннего класса, если внешний тестовый класс работает в режимеPER_CLASS; используйте параметры метода или отдельный жизненный цикл для вложенного класса.