Description¶
Модуль предоставляет Extension
для JUnit5
для тестирования приложений.
Dependency¶
testImplementation "ru.tinkoff.kora:test-junit5"
Удостовериться что включена платформа JUnit
в Gradle:
test {
useJUnitPlatform()
}
Given¶
Примеры будут показаны относительно такой конфигурации классов:
Предположим есть класс @Component
:
@Component
final class Component1 implements Supplier<String> {
public String get() {
return "1";
}
}
Также класс @Component
который является Root
и используется @Component
объявленный выше:
@Root
@Component
final class Component12 implements Lifecycle, Supplier<String> {
private final Component1 component1;
public Component12(Component1 component1) { this.component1 = component1; }
public String get() { return component1.get() + "2"; }
@Override
public Mono<?> init() { return Mono.empty(); }
@Override
public Mono<?> release() { return Mono.empty(); }
}
Также @KoraApp
класс:
@KoraApp
public interface ApplicationModules {
@Tag(Supplier.class)
Supplier<String> taggedSupplier() {
return () -> "tag1";
}
}
KoraAppTest¶
Предполагается использовать аннотацию @KoraAppTest
для аннотирования тестового класса.
Параметры аннотации @KoraAppTest
:
application
- обязательный параметр который указывает на класс аннотированный@KoraApp
, представляющий собой граф всех зависимостей которые будут доступны в рамках теста.components
- список компонентов которые надо инициализировать в рамках теста, можно указать список компонентов и только они и будут инициализированы в рамках графа, в случае отсутствия указанных компонентов, будет инициализирован весь граф.initializeMode
- когда инициализировать контекст графа, каждый раз для каждого тестового метода (стандартное поведение) либо один раз на тестовый класс.
Пример:
@KoraAppTest(
value = ApplicationModules.class,
components = {Component1.class, Component12.class})
class ComponentJUnitExtensionTests {
}
TestComponent¶
Для использования компонентов в рамках теста предлагается использовать аннотацию @TestComponent
которая позволяет внедрять зависимости компонентов в аргументы и/или поля тестового класса.
Пример теста, где компоненты внедряются в поля:
@KoraAppTest(
value = ApplicationModules.class,
components = {Component1.class, Component12.class})
class ComponentJUnitExtensionTests {
@TestComponent
private Component1 component1;
@Test
void example() {
assertEquals("1", component1.get());
}
}
Пример теста, где компоненты внедряются в аргументы метода:
@KoraAppTest(
value = ApplicationModules.class,
components = {Component1.class, Component12.class})
class ComponentJUnitExtensionTests {
@Test
void example(@TestComponent Component1 component1) {
assertEquals("1", component1.get());
}
}
Tags¶
Для внедрения зависимости которая имеет @Tag
, требуется указать соответствующую аннотацию @Tag
рядом с внедряемым аргументом:
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests {
@Test
void example(@Tag(Supplier.class) @TestComponent Supplier<String> supplier) {
assertEquals("tag1", supplier.get());
}
}
MockComponent¶
Для Mock
'а компонентов в рамках теста предлагается использовать аннотацию @MockComponent
которая позволяет сделать Mock
проаннотированного компонента и внедрять Mock
зависимость в аргументы и/или поля тестового класса.
Пример теста, где Mock
внедряется в поля:
@KoraAppTest(
value = ApplicationModules.class,
components = {Component1.class, Component12.class})
class ComponentJUnitExtensionTests {
@MockComponent
private Component1 component1;
@BeforeEach
void mock() {
Mockito.when(component1.get()).thenReturn("?");
}
@Test
void example() {
assertEquals("?", component1.get());
}
}
Пример теста, где Mock
внедряется в аргументы метода:
@KoraAppTest(
value = ApplicationModules.class,
components = {Component1.class, Component12.class})
class ComponentJUnitExtensionTests {
@Test
void example(@MockComponent Component1 component1) {
Mockito.when(component1.get()).thenReturn("?");
assertEquals("?", component1.get());
}
}
Tags¶
Для внедрения зависимости которая имеет @Tag
, требуется указать соответствующую аннотацию @Tag
рядом с внедряемым аргументом:
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests {
@Test
void example(@Tag(Supplier.class) @MockComponent Supplier<String> supplier) {
Mockito.when(supplier.get()).thenReturn("?");
assertEquals("?", supplier.get());
}
}
Config Modifier¶
Для изменений/добавления конфига в рамках тестов предполагается чтобы тестовый класс реализовал интерфейс KoraAppTestConfigModifier
,
где требуется реализовать метод предоставления модификации конфига.
Пример добавления конфига application.conf
в виде строки выглядеть так:
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests implements KoraAppTestConfigModifier {
@Override
public @Nonnull KoraConfigModification config() {
return KoraConfigModification.ofString("""
myconfig {
myproperty = 1
}
""");
}
}
Graph Modifier¶
Для добавления/подмены/мока компонентов в рамках графа можно реализовать интерфейс KoraAppTestGraphModifier
,
где требуется реализовать метод предоставления модификации графа приложения.
Add¶
Пример добавления компонента в граф:
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests 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());
}
}
Replace¶
Пример замены компонента в графе:
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.addComponent(TypeRef.of(Supplier.class, String.class), List.of(Supplier.class), () -> (Supplier<String>) () -> "?");
}
@Test
void example(@Tag(Supplier.class) @TestComponent Supplier<String> supplier) {
assertEquals(1, supplier.get());
}
}
Mock¶
Пример замены компонента в графе:
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests implements KoraAppTestGraphModifier {
@Override
public @Nonnull KoraGraphModification graph() {
return KoraGraphModification.create()
.mockComponent(Component1.class);
}
@Test
void example(@TestComponent Component1 component1) {
Mockito.when(component1.get()).thenReturn("?");
assertEquals("?", component1.get());
}
}
Examples¶
TestContainers & Postgres¶
Пример написания теста с использованием @TestContainers
и базы данных Postgres
с использованием миграции через Flyway.
Предположим что мы имеем класс ApplicationModules:
@KoraApp
public interface JdbcApplicationModules extends
ConfigModule,
LogbackModule,
JdbcDatabaseModule {}
Предположим что мы имеем класс Repository:
@Repository
public interface EntityJdbcRepository extends JdbcRepository {
record Entity(String id, String field1) {}
@Query("""
INSERT INTO entities(id, value1)
VALUES (:entity.id, :entity.field1)
""")
void insert(Entity entity);
@Query("DELETE FROM entities")
int deleteAll();
}
Предположим что миграции лежат в стандартном пакете для Flyway: /resources/db/migration
Пример тестового класса:
@Testcontainers
@KoraAppTest(value = ApplicationModules.class)
class ComponentJUnitExtensionTests implements KoraAppTestConfigModifier {
@Container
private static final PostgreSQLContainer container = new PostgreSQLContainer<>(DockerImageName.parse("postgres:14.7-alpine"))
.withDatabaseName("postgres")
.withUsername("postgres")
.withPassword("postgres")
.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(PostgreSQLContainer.class)))
.waitingFor(Wait.forListeningPort());
@Override
public @Nonnull KoraConfigModification config() {
return KoraConfigModification.ofString("""
db {
jdbcUrl = "%s"
username = "%s"
password = "%s"
maxPoolSize = 10
poolName = "example"
}
""".formatted(container.getJdbcUrl(), "postgres", "postgres"));
}
@BeforeAll
static void migrate() {
Flyway.configure()
.dataSource(container.getJdbcUrl(), "postgres", "postgres")
.load()
.migrate();
}
@BeforeEach
void cleanup(@TestComponent EntityJdbcRepository repository) {
repository.deleteAll();
}
@Test
void example(@TestComponent EntityJdbcRepository repository) {
repository.insert(new EntityJdbcRepository.EntityPart("1", "2"));
}
}