Skip to content

Commit 7cda115

Browse files
committed
QuarkusComponentTest: add basic support for nested test classes
- resolves #46449
1 parent 1ba5ca1 commit 7cda115

File tree

10 files changed

+526
-105
lines changed

10 files changed

+526
-105
lines changed

docs/src/main/asciidoc/testing-components.adoc

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,59 @@ public class FooTest {
232232
Sometimes you need the full control over the bean attributes and maybe even configure the default mock behavior.
233233
You can use the mock configurator API via the `QuarkusComponentTestExtensionBuilder#mock()` method.
234234

235+
== Nested Tests
236+
237+
JUnit 5 https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested[@Nested tests] may help to structure more complex test scenarios.
238+
However, only basic use cases are tested with `@QuarkusComponentTest`.
239+
240+
.Nested test
241+
[source, java]
242+
----
243+
import static org.junit.jupiter.api.Assertions.assertEquals;
244+
245+
import jakarta.inject.Inject;
246+
import io.quarkus.test.InjectMock;
247+
import io.quarkus.test.component.TestConfigProperty;
248+
import io.quarkus.test.component.QuarkusComponentTest;
249+
import org.junit.jupiter.api.Test;
250+
import org.mockito.Mockito;
251+
252+
@QuarkusComponentTest <1>
253+
@TestConfigProperty(key = "bar", value = "true") <2>
254+
public class FooTest {
255+
256+
@Inject
257+
Foo foo; <3>
258+
259+
@InjectMock
260+
Charlie charlieMock; <4>
261+
262+
@Nested
263+
class PingTest {
264+
265+
@Test
266+
public void testPing() {
267+
Mockito.when(charlieMock.ping()).thenReturn("OK");
268+
assertEquals("OK", foo.ping());
269+
}
270+
}
271+
272+
@Nested
273+
class PongTest {
274+
275+
@Test
276+
public void testPong() {
277+
Mockito.when(charlieMock.pong()).thenReturn("NOK");
278+
assertEquals("NOK", foo.pong());
279+
}
280+
}
281+
}
282+
----
283+
<1> The `QuarkusComponentTest` annotation registers the JUnit extension.
284+
<2> Sets a configuration property for the test.
285+
<3> The test injects the component under the test. `Foo` injects `Charlie`.
286+
<4> The test also injects a mock for `Charlie`. The injected reference is an "unconfigured" Mockito mock.
287+
235288
== Configuration
236289

237290
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
240293
It is also possible to set configuration properties for a test method with the `@io.quarkus.test.component.TestConfigProperty` annotation.
241294
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.
242295

296+
NOTE: `@io.quarkus.test.component.TestConfigProperty` declared on a `@Nested` test class is always ignored.
297+
243298
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.
244299

245300
== Mocking CDI Interceptors

test-framework/junit5-component/src/main/java/io/quarkus/test/component/QuarkusComponentTestConfiguration.java

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.eclipse.microprofile.config.spi.Converter;
2929
import org.jboss.logging.Logger;
30+
import org.junit.jupiter.api.Nested;
3031
import org.mockito.Mock;
3132

3233
import io.quarkus.arc.InjectableInstance;
@@ -101,6 +102,12 @@ QuarkusComponentTestConfiguration update(Class<?> testClass) {
101102
List<AnnotationsTransformer> annotationsTransformers = new ArrayList<>(this.annotationsTransformers);
102103
List<Converter<?>> configConverters = new ArrayList<>(this.configConverters);
103104

105+
if (testClass.isAnnotationPresent(Nested.class)) {
106+
while (testClass.getEnclosingClass() != null) {
107+
testClass = testClass.getEnclosingClass();
108+
}
109+
}
110+
104111
QuarkusComponentTest testAnnotation = testClass.getAnnotation(QuarkusComponentTest.class);
105112
if (testAnnotation != null) {
106113
Collections.addAll(componentClasses, testAnnotation.value());
@@ -130,67 +137,78 @@ QuarkusComponentTestConfiguration update(Class<?> testClass) {
130137
}
131138
Class<?> current = testClass;
132139
while (current != null && current != Object.class) {
133-
// All fields annotated with @Inject represent component classes
134-
for (Field field : current.getDeclaredFields()) {
135-
if (field.isAnnotationPresent(Inject.class)) {
136-
if (Instance.class.isAssignableFrom(field.getType())
137-
|| QuarkusComponentTestExtension.isListAllInjectionPoint(field.getGenericType(),
138-
field.getAnnotations(),
139-
field)) {
140-
// Special handling for Instance<Foo> and @All List<Foo>
141-
componentClasses
142-
.add(getRawType(
143-
QuarkusComponentTestExtension.getFirstActualTypeArgument(field.getGenericType())));
144-
} else if (!resolvesToBuiltinBean(field.getType())) {
145-
componentClasses.add(field.getType());
146-
}
140+
collectComponents(current, addNestedClassesAsComponents, componentClasses);
141+
current = current.getSuperclass();
142+
}
143+
144+
// @TestConfigProperty annotations
145+
for (TestConfigProperty testConfigProperty : testClass.getAnnotationsByType(TestConfigProperty.class)) {
146+
configProperties.put(testConfigProperty.key(), testConfigProperty.value());
147+
}
148+
149+
return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), Set.copyOf(componentClasses),
150+
this.mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal,
151+
List.copyOf(annotationsTransformers), List.copyOf(configConverters), configBuilderCustomizer);
152+
}
153+
154+
private static void collectComponents(Class<?> testClass, boolean addNestedClassesAsComponents,
155+
List<Class<?>> componentClasses) {
156+
// All fields annotated with @Inject represent component classes
157+
for (Field field : testClass.getDeclaredFields()) {
158+
if (field.isAnnotationPresent(Inject.class)) {
159+
if (Instance.class.isAssignableFrom(field.getType())
160+
|| QuarkusComponentTestExtension.isListAllInjectionPoint(field.getGenericType(),
161+
field.getAnnotations(),
162+
field)) {
163+
// Special handling for Instance<Foo> and @All List<Foo>
164+
componentClasses
165+
.add(getRawType(
166+
QuarkusComponentTestExtension.getFirstActualTypeArgument(field.getGenericType())));
167+
} else if (!resolvesToBuiltinBean(field.getType())) {
168+
componentClasses.add(field.getType());
147169
}
148170
}
149-
// All static nested classes declared on the test class are components
150-
if (addNestedClassesAsComponents) {
151-
for (Class<?> declaredClass : current.getDeclaredClasses()) {
152-
if (Modifier.isStatic(declaredClass.getModifiers())) {
153-
componentClasses.add(declaredClass);
154-
}
171+
}
172+
// All static nested classes declared on the test class are components
173+
if (addNestedClassesAsComponents) {
174+
for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
175+
if (Modifier.isStatic(declaredClass.getModifiers())) {
176+
componentClasses.add(declaredClass);
155177
}
156178
}
157-
// All params of test methods but:
158-
// - not covered by built-in extensions
159-
// - not annotated with @InjectMock, @SkipInject, @org.mockito.Mock
160-
for (Method method : current.getDeclaredMethods()) {
161-
if (QuarkusComponentTestExtension.isTestMethod(method)) {
162-
for (Parameter param : method.getParameters()) {
163-
if (QuarkusComponentTestExtension.BUILTIN_PARAMETER.test(param)
164-
|| param.isAnnotationPresent(InjectMock.class)
165-
|| param.isAnnotationPresent(SkipInject.class)
166-
|| param.isAnnotationPresent(Mock.class)) {
167-
continue;
168-
}
169-
if (Instance.class.isAssignableFrom(param.getType())
170-
|| QuarkusComponentTestExtension.isListAllInjectionPoint(param.getParameterizedType(),
171-
param.getAnnotations(),
172-
param)) {
173-
// Special handling for Instance<Foo> and @All List<Foo>
174-
componentClasses.add(getRawType(
175-
QuarkusComponentTestExtension.getFirstActualTypeArgument(param.getParameterizedType())));
176-
} else {
177-
componentClasses.add(param.getType());
178-
}
179+
}
180+
// All params of test methods but:
181+
// - not covered by built-in extensions
182+
// - not annotated with @InjectMock, @SkipInject, @org.mockito.Mock
183+
for (Method method : testClass.getDeclaredMethods()) {
184+
if (QuarkusComponentTestExtension.isTestMethod(method)) {
185+
for (Parameter param : method.getParameters()) {
186+
if (QuarkusComponentTestExtension.BUILTIN_PARAMETER.test(param)
187+
|| param.isAnnotationPresent(InjectMock.class)
188+
|| param.isAnnotationPresent(SkipInject.class)
189+
|| param.isAnnotationPresent(Mock.class)) {
190+
continue;
191+
}
192+
if (Instance.class.isAssignableFrom(param.getType())
193+
|| QuarkusComponentTestExtension.isListAllInjectionPoint(param.getParameterizedType(),
194+
param.getAnnotations(),
195+
param)) {
196+
// Special handling for Instance<Foo> and @All List<Foo>
197+
componentClasses.add(getRawType(
198+
QuarkusComponentTestExtension.getFirstActualTypeArgument(param.getParameterizedType())));
199+
} else {
200+
componentClasses.add(param.getType());
179201
}
180202
}
181203
}
182-
current = current.getSuperclass();
183204
}
184205

185-
List<TestConfigProperty> testConfigProperties = new ArrayList<>();
186-
Collections.addAll(testConfigProperties, testClass.getAnnotationsByType(TestConfigProperty.class));
187-
for (TestConfigProperty testConfigProperty : testConfigProperties) {
188-
configProperties.put(testConfigProperty.key(), testConfigProperty.value());
206+
// All @Nested inner classes
207+
for (Class<?> nested : testClass.getDeclaredClasses()) {
208+
if (nested.isAnnotationPresent(Nested.class) && !Modifier.isStatic(nested.getModifiers())) {
209+
collectComponents(nested, addNestedClassesAsComponents, componentClasses);
210+
}
189211
}
190-
191-
return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), Set.copyOf(componentClasses),
192-
this.mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal,
193-
List.copyOf(annotationsTransformers), List.copyOf(configConverters), configBuilderCustomizer);
194212
}
195213

196214
QuarkusComponentTestConfiguration update(Method testMethod) {

0 commit comments

Comments
 (0)