From 7cda115b3cb839f703fa6f2535b1d2fad9e065f7 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 25 Feb 2025 14:07:20 +0100 Subject: [PATCH] QuarkusComponentTest: add basic support for nested test classes - resolves #46449 --- .../src/main/asciidoc/testing-components.adoc | 55 +++++++ .../QuarkusComponentTestConfiguration.java | 120 ++++++++------ .../QuarkusComponentTestExtension.java | 146 ++++++++++++------ .../test/component/TestConfigProperty.java | 7 +- .../component/nested/NestedNestedTest.java | 53 +++++++ .../nested/NestedParamInjectTest.java | 31 ++++ .../test/component/nested/NestedTest.java | 60 +++++++ .../nested/TopPerClassNestedPerClassTest.java | 58 +++++++ .../TopPerClassNestedPerMethodTest.java | 55 +++++++ .../TopPerMethodNestedPerClassTest.java | 46 ++++++ 10 files changed, 526 insertions(+), 105 deletions(-) create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedNestedTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedParamInjectTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerClassTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerMethodTest.java create mode 100644 test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerMethodNestedPerClassTest.java diff --git a/docs/src/main/asciidoc/testing-components.adoc b/docs/src/main/asciidoc/testing-components.adoc index 6215ccfee6702..03bf5aa3e119c 100644 --- a/docs/src/main/asciidoc/testing-components.adoc +++ b/docs/src/main/asciidoc/testing-components.adoc @@ -232,6 +232,59 @@ public class FooTest { Sometimes you need the full control over the bean attributes and maybe even configure the default mock behavior. You can use the mock configurator API via the `QuarkusComponentTestExtensionBuilder#mock()` method. +== Nested Tests + +JUnit 5 https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested[@Nested tests] may help to structure more complex test scenarios. +However, only basic use cases are tested with `@QuarkusComponentTest`. + +.Nested test +[source, java] +---- +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.QuarkusComponentTest; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +@QuarkusComponentTest <1> +@TestConfigProperty(key = "bar", value = "true") <2> +public class FooTest { + + @Inject + Foo foo; <3> + + @InjectMock + Charlie charlieMock; <4> + + @Nested + class PingTest { + + @Test + public void testPing() { + Mockito.when(charlieMock.ping()).thenReturn("OK"); + assertEquals("OK", foo.ping()); + } + } + + @Nested + class PongTest { + + @Test + public void testPong() { + Mockito.when(charlieMock.pong()).thenReturn("NOK"); + assertEquals("NOK", foo.pong()); + } + } +} +---- +<1> The `QuarkusComponentTest` annotation registers the JUnit extension. +<2> Sets a configuration property for the test. +<3> The test injects the component under the test. `Foo` injects `Charlie`. +<4> The test also injects a mock for `Charlie`. The injected reference is an "unconfigured" Mockito mock. + == Configuration You can set the configuration properties for a test with the `@io.quarkus.test.component.TestConfigProperty` annotation or with the `QuarkusComponentTestExtensionBuilder#configProperty(String, String)` method. @@ -240,6 +293,8 @@ If you only need to use the default values for missing config properties, then t It is also possible to set configuration properties for a test method with the `@io.quarkus.test.component.TestConfigProperty` annotation. However, if the test instance lifecycle is `Lifecycle#_PER_CLASS` this annotation can only be used on the test class and is ignored on test methods. +NOTE: `@io.quarkus.test.component.TestConfigProperty` declared on a `@Nested` test class is always ignored. + CDI beans are also automatically registered for all injected https://smallrye.io/smallrye-config/Main/config/mappings/[Config Mappings]. The mappings are populated with the test configuration properties. == Mocking CDI Interceptors 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 ef3e8ec2ab65b..00196d8fe9ac7 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 @@ -27,6 +27,7 @@ import org.eclipse.microprofile.config.spi.Converter; import org.jboss.logging.Logger; +import org.junit.jupiter.api.Nested; import org.mockito.Mock; import io.quarkus.arc.InjectableInstance; @@ -101,6 +102,12 @@ QuarkusComponentTestConfiguration update(Class testClass) { List annotationsTransformers = new ArrayList<>(this.annotationsTransformers); List> configConverters = new ArrayList<>(this.configConverters); + if (testClass.isAnnotationPresent(Nested.class)) { + while (testClass.getEnclosingClass() != null) { + testClass = testClass.getEnclosingClass(); + } + } + QuarkusComponentTest testAnnotation = testClass.getAnnotation(QuarkusComponentTest.class); if (testAnnotation != null) { Collections.addAll(componentClasses, testAnnotation.value()); @@ -130,67 +137,78 @@ QuarkusComponentTestConfiguration update(Class testClass) { } Class current = testClass; while (current != null && current != Object.class) { - // All fields annotated with @Inject represent component classes - for (Field field : current.getDeclaredFields()) { - if (field.isAnnotationPresent(Inject.class)) { - if (Instance.class.isAssignableFrom(field.getType()) - || QuarkusComponentTestExtension.isListAllInjectionPoint(field.getGenericType(), - field.getAnnotations(), - field)) { - // Special handling for Instance and @All List - componentClasses - .add(getRawType( - QuarkusComponentTestExtension.getFirstActualTypeArgument(field.getGenericType()))); - } else if (!resolvesToBuiltinBean(field.getType())) { - componentClasses.add(field.getType()); - } + collectComponents(current, addNestedClassesAsComponents, componentClasses); + current = current.getSuperclass(); + } + + // @TestConfigProperty annotations + for (TestConfigProperty testConfigProperty : testClass.getAnnotationsByType(TestConfigProperty.class)) { + configProperties.put(testConfigProperty.key(), testConfigProperty.value()); + } + + return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), Set.copyOf(componentClasses), + this.mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal, + List.copyOf(annotationsTransformers), List.copyOf(configConverters), configBuilderCustomizer); + } + + private static void collectComponents(Class testClass, boolean addNestedClassesAsComponents, + List> componentClasses) { + // All fields annotated with @Inject represent component classes + for (Field field : testClass.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class)) { + if (Instance.class.isAssignableFrom(field.getType()) + || QuarkusComponentTestExtension.isListAllInjectionPoint(field.getGenericType(), + field.getAnnotations(), + field)) { + // Special handling for Instance and @All List + componentClasses + .add(getRawType( + QuarkusComponentTestExtension.getFirstActualTypeArgument(field.getGenericType()))); + } else if (!resolvesToBuiltinBean(field.getType())) { + componentClasses.add(field.getType()); } } - // All static nested classes declared on the test class are components - if (addNestedClassesAsComponents) { - for (Class declaredClass : current.getDeclaredClasses()) { - if (Modifier.isStatic(declaredClass.getModifiers())) { - componentClasses.add(declaredClass); - } + } + // All static nested classes declared on the test class are components + if (addNestedClassesAsComponents) { + for (Class declaredClass : testClass.getDeclaredClasses()) { + if (Modifier.isStatic(declaredClass.getModifiers())) { + componentClasses.add(declaredClass); } } - // All params of test methods but: - // - not covered by built-in extensions - // - not annotated with @InjectMock, @SkipInject, @org.mockito.Mock - for (Method method : current.getDeclaredMethods()) { - if (QuarkusComponentTestExtension.isTestMethod(method)) { - for (Parameter param : method.getParameters()) { - if (QuarkusComponentTestExtension.BUILTIN_PARAMETER.test(param) - || param.isAnnotationPresent(InjectMock.class) - || param.isAnnotationPresent(SkipInject.class) - || param.isAnnotationPresent(Mock.class)) { - continue; - } - if (Instance.class.isAssignableFrom(param.getType()) - || QuarkusComponentTestExtension.isListAllInjectionPoint(param.getParameterizedType(), - param.getAnnotations(), - param)) { - // Special handling for Instance and @All List - componentClasses.add(getRawType( - QuarkusComponentTestExtension.getFirstActualTypeArgument(param.getParameterizedType()))); - } else { - componentClasses.add(param.getType()); - } + } + // All params of test methods but: + // - not covered by built-in extensions + // - not annotated with @InjectMock, @SkipInject, @org.mockito.Mock + for (Method method : testClass.getDeclaredMethods()) { + if (QuarkusComponentTestExtension.isTestMethod(method)) { + for (Parameter param : method.getParameters()) { + if (QuarkusComponentTestExtension.BUILTIN_PARAMETER.test(param) + || param.isAnnotationPresent(InjectMock.class) + || param.isAnnotationPresent(SkipInject.class) + || param.isAnnotationPresent(Mock.class)) { + continue; + } + if (Instance.class.isAssignableFrom(param.getType()) + || QuarkusComponentTestExtension.isListAllInjectionPoint(param.getParameterizedType(), + param.getAnnotations(), + param)) { + // Special handling for Instance and @All List + componentClasses.add(getRawType( + QuarkusComponentTestExtension.getFirstActualTypeArgument(param.getParameterizedType()))); + } else { + componentClasses.add(param.getType()); } } } - current = current.getSuperclass(); } - List testConfigProperties = new ArrayList<>(); - Collections.addAll(testConfigProperties, testClass.getAnnotationsByType(TestConfigProperty.class)); - for (TestConfigProperty testConfigProperty : testConfigProperties) { - configProperties.put(testConfigProperty.key(), testConfigProperty.value()); + // All @Nested inner classes + for (Class nested : testClass.getDeclaredClasses()) { + if (nested.isAnnotationPresent(Nested.class) && !Modifier.isStatic(nested.getModifiers())) { + collectComponents(nested, addNestedClassesAsComponents, componentClasses); + } } - - return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), Set.copyOf(componentClasses), - this.mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal, - List.copyOf(annotationsTransformers), List.copyOf(configConverters), configBuilderCustomizer); } QuarkusComponentTestConfiguration update(Method testMethod) { 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 bf598bd71f352..6944ddb55d6d4 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 @@ -188,6 +188,7 @@ public static QuarkusComponentTestExtensionBuilder builder() { private static final String KEY_CONFIG = "config"; private static final String KEY_TEST_CLASS_CONFIG = "testClassConfig"; private static final String KEY_CONFIG_MAPPINGS = "configMappings"; + private static final String KEY_CONTAINER_STATE = "containerState"; private static final String QUARKUS_TEST_COMPONENT_OUTPUT_DIRECTORY = "quarkus.test.component.output-directory"; @@ -225,7 +226,6 @@ public void beforeAll(ExtensionContext context) throws Exception { @Override public void afterAll(ExtensionContext context) throws Exception { long start = System.nanoTime(); - // Stop the container if Lifecycle.PER_CLASS is used stopContainer(context, Lifecycle.PER_CLASS); cleanup(context); LOG.debugf("afterAll: %s ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); @@ -366,17 +366,25 @@ private void destroyDependentTestMethodParams(ExtensionContext context) { } private void buildContainer(ExtensionContext context) { + if (getContainerState(context) != ContainerState.UNINITIALIZED) { + return; + } QuarkusComponentTestConfiguration testClassConfiguration = baseConfiguration .update(context.getRequiredTestClass()); store(context).put(KEY_TEST_CLASS_CONFIG, testClassConfiguration); ClassLoader oldTccl = initArcContainer(context, testClassConfiguration); store(context).put(KEY_OLD_TCCL, oldTccl); + setContainerState(context, ContainerState.INITIALIZED); } @SuppressWarnings("unchecked") private void cleanup(ExtensionContext context) { + if (getContainerState(context) != ContainerState.STOPPED) { + return; + } ClassLoader oldTccl = store(context).get(KEY_OLD_TCCL, ClassLoader.class); Thread.currentThread().setContextClassLoader(oldTccl); + store(context).remove(KEY_OLD_TCCL); store(context).remove(KEY_CONFIG_MAPPINGS); Set generatedResources = store(context).get(KEY_GENERATED_RESOURCES, Set.class); for (Path path : generatedResources) { @@ -387,14 +395,19 @@ private void cleanup(ExtensionContext context) { LOG.errorf("Unable to delete the generated resource %s: ", path, e.getMessage()); } } + store(context).remove(KEY_GENERATED_RESOURCES); + setContainerState(context, ContainerState.UNINITIALIZED); } @SuppressWarnings("unchecked") private void stopContainer(ExtensionContext context, Lifecycle testInstanceLifecycle) throws Exception { if (testInstanceLifecycle.equals(context.getTestInstanceLifecycle().orElse(Lifecycle.PER_METHOD))) { + if (getContainerState(context) != ContainerState.STARTED) { + return; + } for (FieldInjector fieldInjector : (List) store(context) .get(KEY_INJECTED_FIELDS, List.class)) { - fieldInjector.unset(context.getRequiredTestInstance()); + fieldInjector.unset(); } try { Arc.shutdown(); @@ -404,64 +417,93 @@ private void stopContainer(ExtensionContext context, Lifecycle testInstanceLifec MockBeanCreator.clear(); ConfigBeanCreator.clear(); InterceptorMethodCreator.clear(); + store(context).remove(KEY_CONTAINER_STATE); SmallRyeConfig config = store(context).get(KEY_CONFIG, SmallRyeConfig.class); ConfigProviderResolver.instance().releaseConfig(config); - ConfigProviderResolver - .setInstance(store(context).get(KEY_OLD_CONFIG_PROVIDER_RESOLVER, - ConfigProviderResolver.class)); + ConfigProviderResolver oldConfigProviderResolver = store(context).get(KEY_OLD_CONFIG_PROVIDER_RESOLVER, + ConfigProviderResolver.class); + ConfigProviderResolver.setInstance(oldConfigProviderResolver); + setContainerState(context, ContainerState.STOPPED); } } + enum ContainerState { + UNINITIALIZED, + INITIALIZED, + STARTED, + STOPPED + } + + private ContainerState getContainerState(ExtensionContext context) { + ContainerState state = store(context).get(KEY_CONTAINER_STATE, ContainerState.class); + return state != null ? state : ContainerState.UNINITIALIZED; + } + + private void setContainerState(ExtensionContext context, ContainerState state) { + store(context).put(KEY_CONTAINER_STATE, state); + } + private void startContainer(ExtensionContext context, Lifecycle testInstanceLifecycle) throws Exception { - if (testInstanceLifecycle.equals(context.getTestInstanceLifecycle().orElse(Lifecycle.PER_METHOD))) { - // Init ArC - Arc.initialize(); - - QuarkusComponentTestConfiguration configuration = store(context).get(KEY_TEST_CLASS_CONFIG, - QuarkusComponentTestConfiguration.class); - Optional testMethod = context.getTestMethod(); - if (testMethod.isPresent()) { - configuration = configuration.update(testMethod.get()); - } + if (!testInstanceLifecycle.equals(context.getTestInstanceLifecycle().orElse(Lifecycle.PER_METHOD))) { + return; + } + ContainerState state = getContainerState(context); + if (state == ContainerState.UNINITIALIZED) { + throw new IllegalStateException("Container not initialized"); + } else if (state == ContainerState.STARTED) { + return; + } + // Init ArC + Arc.initialize(); + + QuarkusComponentTestConfiguration configuration = store(context).get(KEY_TEST_CLASS_CONFIG, + QuarkusComponentTestConfiguration.class); + Optional testMethod = context.getTestMethod(); + if (testMethod.isPresent()) { + configuration = configuration.update(testMethod.get()); + } - ConfigProviderResolver oldConfigProviderResolver = ConfigProviderResolver.instance(); - store(context).put(KEY_OLD_CONFIG_PROVIDER_RESOLVER, oldConfigProviderResolver); - - SmallRyeConfigProviderResolver smallRyeConfigProviderResolver = new SmallRyeConfigProviderResolver(); - ConfigProviderResolver.setInstance(smallRyeConfigProviderResolver); - - // TCCL is now the QuarkusComponentTestClassLoader set during initialization - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - SmallRyeConfigBuilder configBuilder = new SmallRyeConfigBuilder().forClassLoader(tccl) - .addDefaultInterceptors() - .withConverters(configuration.configConverters.toArray(new Converter[] {})) - .addDefaultSources() - .withSources( - new QuarkusComponentTestConfigSource(configuration.configProperties, - configuration.configSourceOrdinal)); - @SuppressWarnings("unchecked") - Set configMappings = store(context).get(KEY_CONFIG_MAPPINGS, Set.class); - if (configMappings != null) { - // Register the mappings found during bean discovery - for (ConfigClass mapping : configMappings) { - configBuilder.withMapping(mapping); - } - } - if (configuration.configBuilderCustomizer != null) { - configuration.configBuilderCustomizer.accept(configBuilder); + ConfigProviderResolver oldConfigProviderResolver = ConfigProviderResolver.instance(); + store(context).put(KEY_OLD_CONFIG_PROVIDER_RESOLVER, oldConfigProviderResolver); + + SmallRyeConfigProviderResolver smallRyeConfigProviderResolver = new SmallRyeConfigProviderResolver(); + ConfigProviderResolver.setInstance(smallRyeConfigProviderResolver); + + // TCCL is now the QuarkusComponentTestClassLoader set during initialization + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + SmallRyeConfigBuilder configBuilder = new SmallRyeConfigBuilder().forClassLoader(tccl) + .addDefaultInterceptors() + .withConverters(configuration.configConverters.toArray(new Converter[] {})) + .addDefaultSources() + .withSources( + new QuarkusComponentTestConfigSource(configuration.configProperties, + configuration.configSourceOrdinal)); + @SuppressWarnings("unchecked") + Set configMappings = store(context).get(KEY_CONFIG_MAPPINGS, Set.class); + if (configMappings != null) { + // Register the mappings found during bean discovery + for (ConfigClass mapping : configMappings) { + configBuilder.withMapping(mapping); } - SmallRyeConfig config = configBuilder.build(); - smallRyeConfigProviderResolver.registerConfig(config, tccl); - store(context).put(KEY_CONFIG, config); - ConfigBeanCreator.setClassLoader(tccl); - - // Inject fields declated on the test class - Object testInstance = context.getRequiredTestInstance(); - store(context).put(KEY_INJECTED_FIELDS, injectFields(context.getRequiredTestClass(), testInstance)); - // Injected test method parameters - store(context).put(KEY_INJECTED_PARAMS, new CopyOnWriteArrayList<>()); } + if (configuration.configBuilderCustomizer != null) { + configuration.configBuilderCustomizer.accept(configBuilder); + } + SmallRyeConfig config = configBuilder.build(); + smallRyeConfigProviderResolver.registerConfig(config, tccl); + store(context).put(KEY_CONFIG, config); + ConfigBeanCreator.setClassLoader(tccl); + + // Inject fields declared on test classes + List injectedFields = new ArrayList<>(); + for (Object testInstance : context.getRequiredTestInstances().getAllInstances()) { + injectedFields.addAll(injectFields(testInstance.getClass(), testInstance)); + } + store(context).put(KEY_INJECTED_FIELDS, injectedFields); + // Injected test method parameters + store(context).put(KEY_INJECTED_PARAMS, new CopyOnWriteArrayList<>()); + setContainerState(context, ContainerState.STARTED); } private Store store(ExtensionContext context) { @@ -1094,11 +1136,13 @@ private List findMethods(Class testClass, Predicate methodPre static class FieldInjector { + private final Object testInstance; private final Field field; private final Runnable unsetAction; public FieldInjector(Field field, Object testInstance) throws Exception { this.field = field; + this.testInstance = testInstance; ArcContainer container = Arc.container(); BeanManager beanManager = container.beanManager(); @@ -1153,7 +1197,7 @@ public FieldInjector(Field field, Object testInstance) throws Exception { field.set(testInstance, injectedInstance); } - void unset(Object testInstance) throws Exception { + void unset() throws Exception { if (unsetAction != null) { unsetAction.run(); } diff --git a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/TestConfigProperty.java b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/TestConfigProperty.java index f898c3adf23c4..1e51114b7fef5 100644 --- a/test-framework/junit5-component/src/main/java/io/quarkus/test/component/TestConfigProperty.java +++ b/test-framework/junit5-component/src/main/java/io/quarkus/test/component/TestConfigProperty.java @@ -15,11 +15,12 @@ /** * Set the value of a configuration property. *

- * If declared on a class then the configuration property is used for all test methods declared on the test class. + * If declared on a top-level test class then the configuration property is used for all test methods declared on the test class + * and all nested test classes. *

* If declared on a method then the configuration property is only used for that test method. - * If the test instance lifecycle is {@link Lifecycle#_PER_CLASS}, this annotation can only be used on the test class and is - * ignored on test methods. + * If the test instance lifecycle is {@link Lifecycle#_PER_CLASS}, this annotation can only be declared on a top-level test + * class, otherwise it's ignored. *

* Configuration properties declared on test methods take precedence over the configuration properties declared on test class. * diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedNestedTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedNestedTest.java new file mode 100644 index 0000000000000..34aa164985e17 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedNestedTest.java @@ -0,0 +1,53 @@ +package io.quarkus.test.component.nested; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.beans.Charlie; +import io.quarkus.test.component.beans.MyComponent; + +@QuarkusComponentTest +@TestConfigProperty(key = "foo", value = "BAR") +public class NestedNestedTest { + + @Inject + MyComponent myComponent; + + @InjectMock + Charlie charlie; + + @Test + public void testPing() { + Mockito.when(charlie.ping()).thenReturn("foo"); + assertEquals("foo and BAR", myComponent.ping()); + } + + @Nested + class Nested1 { + + @Test + public void testPing1() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + + @Nested + class Nested2 { + + @Test + @TestConfigProperty(key = "foo", value = "RAB") + public void testPing2() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and RAB", myComponent.ping()); + } + } + } +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedParamInjectTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedParamInjectTest.java new file mode 100644 index 0000000000000..d4c9b2e6b1e52 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedParamInjectTest.java @@ -0,0 +1,31 @@ +package io.quarkus.test.component.nested; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.beans.Charlie; +import io.quarkus.test.component.beans.MyComponent; + +@QuarkusComponentTest +@TestConfigProperty(key = "foo", value = "BAR") +public class NestedParamInjectTest { + + @InjectMock + Charlie charlie; + + @Nested + class Nested1 { + + @Test + public void testPing1(MyComponent myComponent) { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedTest.java new file mode 100644 index 0000000000000..86b8ef2e905a8 --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/NestedTest.java @@ -0,0 +1,60 @@ +package io.quarkus.test.component.nested; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.beans.Charlie; +import io.quarkus.test.component.beans.MyComponent; + +@QuarkusComponentTest +@TestConfigProperty(key = "foo", value = "BAR") +public class NestedTest { + + @Inject + MyComponent myComponent; + + @InjectMock + Charlie charlie; + + @Test + public void testPing() { + Mockito.when(charlie.ping()).thenReturn("foo"); + assertEquals("foo and BAR", myComponent.ping()); + } + + @TestConfigProperty(key = "foo", value = "BANG") // declared on nested test class -> ignored + @Nested + class Nested1 { + + @InjectMock + Charlie charlie; + + @Test + public void testPing1() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } + + @Nested + class Nested2 { + + @Inject + MyComponent myComponent; + + @Test + @TestConfigProperty(key = "foo", value = "RAB") + public void testPing2() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and RAB", myComponent.ping()); + } + } +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerClassTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerClassTest.java new file mode 100644 index 0000000000000..e9237f643effc --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerClassTest.java @@ -0,0 +1,58 @@ +package io.quarkus.test.component.nested; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.mockito.Mockito; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.beans.Charlie; +import io.quarkus.test.component.beans.MyComponent; + +@TestInstance(Lifecycle.PER_CLASS) +@QuarkusComponentTest +@TestConfigProperty(key = "foo", value = "BAR") +public class TopPerClassNestedPerClassTest { + + @Inject + MyComponent myComponent; + + @InjectMock + Charlie charlie; + + @Test + public void testPing() { + Mockito.when(charlie.ping()).thenReturn("foo"); + assertEquals("foo and BAR", myComponent.ping()); + } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + class Nested1 { + + @Test + public void testPing1() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + class Nested2 { + + @Test + @TestConfigProperty(key = "foo", value = "IGNORED") // PER_CLASS -> ignored + public void testPing2() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerMethodTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerMethodTest.java new file mode 100644 index 0000000000000..9ac5acca466ca --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerClassNestedPerMethodTest.java @@ -0,0 +1,55 @@ +package io.quarkus.test.component.nested; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.mockito.Mockito; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.beans.Charlie; +import io.quarkus.test.component.beans.MyComponent; + +@TestInstance(Lifecycle.PER_CLASS) +@QuarkusComponentTest +@TestConfigProperty(key = "foo", value = "BAR") +public class TopPerClassNestedPerMethodTest { + + @Inject + MyComponent myComponent; + + @InjectMock + Charlie charlie; + + @Test + public void testPing() { + Mockito.when(charlie.ping()).thenReturn("foo"); + assertEquals("foo and BAR", myComponent.ping()); + } + + @Nested + class Nested1 { + + @Test + public void testPing1() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } + + @Nested + class Nested2 { + + @Test + public void testPing2() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } +} diff --git a/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerMethodNestedPerClassTest.java b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerMethodNestedPerClassTest.java new file mode 100644 index 0000000000000..6e5384cb4f84c --- /dev/null +++ b/test-framework/junit5-component/src/test/java/io/quarkus/test/component/nested/TopPerMethodNestedPerClassTest.java @@ -0,0 +1,46 @@ +package io.quarkus.test.component.nested; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.mockito.Mockito; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.component.QuarkusComponentTest; +import io.quarkus.test.component.TestConfigProperty; +import io.quarkus.test.component.beans.Charlie; +import io.quarkus.test.component.beans.MyComponent; + +@QuarkusComponentTest +@TestConfigProperty(key = "foo", value = "BAR") +public class TopPerMethodNestedPerClassTest { + + @Inject + MyComponent myComponent; + + @InjectMock + Charlie charlie; + + @Test + public void testPing() { + Mockito.when(charlie.ping()).thenReturn("foo"); + assertEquals("foo and BAR", myComponent.ping()); + } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + class Nested1 { + + @Test + public void testPing1() { + Mockito.when(charlie.ping()).thenReturn("baz"); + assertEquals("baz and BAR", myComponent.ping()); + } + } + +}