From 54a9ea3e9a076e5c5a136da4ab8f206b164fd6fa Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 21 Feb 2024 16:21:38 +0100 Subject: [PATCH 1/2] QuarkusComponentTest: support parameterized test methods - arguments of a parameterized test method that are provided by an ArgumentsProvider must be annotated with SkipInject --- .../asciidoc/getting-started-testing.adoc | 3 ++ test-framework/junit5-component/pom.xml | 5 ++ .../QuarkusComponentTestConfiguration.java | 3 +- .../QuarkusComponentTestExtension.java | 12 +++-- .../ParameterInjectionParameterizedTest.java | 47 +++++++++++++++++++ 5 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionParameterizedTest.java diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 63ef64af6eeb1..9d62dc16b4d58 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1666,11 +1666,14 @@ The fields annotated with `@Inject` and `@InjectMock` are injected after a test Finally, the CDI request context is activated and terminated per each test method. === Injection + Test class fields annotated with `@jakarta.inject.Inject` and `@io.quarkus.test.InjectMock` are injected after a test instance is created. Dependent beans injected into these fields are correctly destroyed before a test instance is destroyed. Parameters of a test method for which a matching bean exists are resolved unless annotated with `@io.quarkus.test.component.SkipInject`. Dependent beans injected into the test method arguments are correctly destroyed after the test method completes. +NOTE: Arguments of a `@ParameterizedTest` method that are provided by an `ArgumentsProvider`, for example with `@org.junit.jupiter.params.provider.ValueArgumentsProvider`, must be annotated with `@SkipInject`. + === Auto Mocking Unsatisfied Dependencies Unlike in regular CDI environments the test does not fail if a component injects an unsatisfied dependency. diff --git a/test-framework/junit5-component/pom.xml b/test-framework/junit5-component/pom.xml index 8d6bb92298438..156a0af5256ae 100644 --- a/test-framework/junit5-component/pom.xml +++ b/test-framework/junit5-component/pom.xml @@ -74,6 +74,11 @@ jakarta.ws.rs-api test + + org.assertj + assertj-core + test + diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java index 4a8bd75c25f80..8279bbf99c84b 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java @@ -18,7 +18,6 @@ import jakarta.inject.Provider; import org.jboss.logging.Logger; -import org.junit.jupiter.api.Test; import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.processor.AnnotationsTransformer; @@ -98,7 +97,7 @@ QuarkusComponentTestConfiguration update(Class testClass) { // - not annotated with @InjectMock // - not annotated with @SkipInject for (Method method : current.getDeclaredMethods()) { - if (method.isAnnotationPresent(Test.class)) { + if (QuarkusComponentTestExtension.isTestMethod(method)) { for (Parameter param : method.getParameters()) { if (QuarkusComponentTestExtension.BUILTIN_PARAMETER.test(param) || param.isAnnotationPresent(InjectMock.class) diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java index 8c1873054e2c3..f4210226f25dd 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -79,6 +80,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; import io.quarkus.arc.All; import io.quarkus.arc.Arc; @@ -269,7 +271,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon // Target is empty for constructor or static method parameterContext.getTarget().isPresent() // Only test methods are supported - && parameterContext.getDeclaringExecutable().isAnnotationPresent(Test.class) + && isTestMethod(parameterContext.getDeclaringExecutable()) // A method/param annotated with @SkipInject is never supported && !parameterContext.isAnnotated(SkipInject.class) && !parameterContext.getDeclaringExecutable().isAnnotationPresent(SkipInject.class) @@ -999,7 +1001,7 @@ private List findInjectFields(Class testClass) { } private List findInjectParams(Class testClass) { - List testMethods = findMethods(testClass, m -> m.isAnnotationPresent(Test.class)); + List testMethods = findMethods(testClass, QuarkusComponentTestExtension::isTestMethod); List ret = new ArrayList<>(); for (Method method : testMethods) { for (Parameter param : method.getParameters()) { @@ -1015,7 +1017,7 @@ private List findInjectParams(Class testClass) { } private List findInjectMockParams(Class testClass) { - List testMethods = findMethods(testClass, m -> m.isAnnotationPresent(Test.class)); + List testMethods = findMethods(testClass, QuarkusComponentTestExtension::isTestMethod); List ret = new ArrayList<>(); for (Method method : testMethods) { for (Parameter param : method.getParameters()) { @@ -1028,6 +1030,10 @@ private List findInjectMockParams(Class testClass) { return ret; } + static boolean isTestMethod(Executable method) { + return method.isAnnotationPresent(Test.class) || method.isAnnotationPresent(ParameterizedTest.class); + } + private List findFields(Class testClass, List> annotations) { List fields = new ArrayList<>(); Class current = testClass; diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionParameterizedTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionParameterizedTest.java new file mode 100644 index 0000000000000..3a3f9f2fa8891 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionParameterizedTest.java @@ -0,0 +1,47 @@ +package io.quarkus.test.component.paraminject; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.SkipInject; + +@QuarkusComponentTest +public class ParameterInjectionParameterizedTest { + + @ParameterizedTest + @ValueSource(strings = { "alpha", "bravo", "delta" }) + public void testParamsInjection(@SkipInject String param, Converter converter) { + Assertions.assertThat(converter.convert(param)).isIn("ALPHA", "BRAVO", "DELTA"); + } + + @AfterAll + public static void afterAll() { + assertEquals(3, Converter.DESTROYED.get()); + } + + @Dependent + public static class Converter { + + static final AtomicInteger DESTROYED = new AtomicInteger(); + + String convert(String param) { + return param.toUpperCase(); + } + + @PreDestroy + void destroy() { + DESTROYED.incrementAndGet(); + } + } + +} From b7a6b489885c4e7819d069556a4d8146fc591283 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 21 Feb 2024 17:21:49 +0100 Subject: [PATCH 2/2] QuarkusComponentTest: support repeated tests --- .../QuarkusComponentTestExtension.java | 5 +- .../ParameterInjectionRepeatedTest.java | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionRepeatedTest.java diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java index f4210226f25dd..0046c09eee626 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestExtension.java @@ -64,6 +64,7 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.logging.Logger; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -1031,7 +1032,9 @@ private List findInjectMockParams(Class testClass) { } static boolean isTestMethod(Executable method) { - return method.isAnnotationPresent(Test.class) || method.isAnnotationPresent(ParameterizedTest.class); + return method.isAnnotationPresent(Test.class) + || method.isAnnotationPresent(ParameterizedTest.class) + || method.isAnnotationPresent(RepeatedTest.class); } private List findFields(Class testClass, List> annotations) { diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionRepeatedTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionRepeatedTest.java new file mode 100644 index 0000000000000..1d4504c4ca798 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/paraminject/ParameterInjectionRepeatedTest.java @@ -0,0 +1,46 @@ +package io.quarkus.test.component.paraminject; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.RepetitionInfo; + +import io.quarkus.test.component.QuarkusComponentTest; + +@QuarkusComponentTest +public class ParameterInjectionRepeatedTest { + @RepeatedTest(10) + public void testParamsInjection( + // component under test + Converter converter, + // ignored automatically, no need for `@SkipInject` + RepetitionInfo info) { + assertEquals(10, info.getTotalRepetitions()); + assertEquals("FOOBAR", converter.convert("fooBar")); + } + + @AfterAll + public static void afterAll() { + assertEquals(10, Converter.DESTROYED.get()); + } + + @Dependent + public static class Converter { + static final AtomicInteger DESTROYED = new AtomicInteger(); + + String convert(String param) { + return param.toUpperCase(); + } + + @PreDestroy + void destroy() { + DESTROYED.incrementAndGet(); + } + } +}