Skip to content

Commit 74521ab

Browse files
committed
Introduce ExtensionContext.getEnclosingTestClasses()
Resolves #4375.
1 parent 1e135b9 commit 74521ab

File tree

10 files changed

+149
-23
lines changed

10 files changed

+149
-23
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.12.1.adoc

+3-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ on GitHub.
4545
[[release-notes-5.12.1-junit-jupiter-new-features-and-improvements]]
4646
==== New Features and Improvements
4747

48-
* ❓
48+
* New `ExtensionContext.getEnclosingTestClasses()` method to help with migration away from
49+
`AnnotationSupport.findAnnotation(Class, Class, SearchOption)` (deprecated since 1.12.0)
50+
to `AnnotationSupport.findAnnotation(Class, Class, List)`.
4951

5052

5153
[[release-notes-5.12.1-junit-vintage]]

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java

+27
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
* <p>{@link Extension Extensions} are provided an instance of
4141
* {@code ExtensionContext} to perform their work.
4242
*
43+
* <p>This interface is not intended to be implemented by clients.
44+
*
4345
* @since 5.0
4446
* @see Store
4547
* @see Namespace
@@ -129,6 +131,31 @@ public interface ExtensionContext {
129131
*/
130132
Optional<Class<?>> getTestClass();
131133

134+
/**
135+
* Get the enclosing test classes of the current test or container.
136+
*
137+
* <p>This method is useful to look up annotations on nested test classes
138+
* and their enclosing <em>runtime</em> types:
139+
*
140+
* <pre>{@code
141+
* AnnotationSupport.findAnnotation(
142+
* extensionContext.getRequiredTestClass(),
143+
* MyAnnotation.class,
144+
* extensionContext.getEnclosingTestClasses()
145+
* );
146+
* }</pre>
147+
*
148+
* @return an empty list if there is no class associated with the current
149+
* test or container or when it is not nested; otherwise, a list containing
150+
* the enclosing test classes in order from outermost to innermost; never
151+
* {@code null}
152+
*
153+
* @since 5.12.1
154+
* @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(Class, Class, List)
155+
*/
156+
@API(status = EXPERIMENTAL, since = "5.12.1")
157+
List<Class<?>> getEnclosingTestClasses();
158+
132159
/**
133160
* Get the <em>required</em> {@link Class} associated with the current test
134161
* or container.

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.lang.reflect.AnnotatedElement;
1414
import java.lang.reflect.Method;
15+
import java.util.List;
1516
import java.util.Optional;
1617

1718
import org.junit.jupiter.api.TestInstance.Lifecycle;
@@ -68,6 +69,11 @@ public Optional<Class<?>> getTestClass() {
6869
return Optional.of(getTestDescriptor().getTestClass());
6970
}
7071

72+
@Override
73+
public List<Class<?>> getEnclosingTestClasses() {
74+
return getTestDescriptor().getEnclosingTestClasses();
75+
}
76+
7177
@Override
7278
public Optional<Lifecycle> getTestInstanceLifecycle() {
7379
return Optional.of(this.lifecycle);

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java

+8
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010

1111
package org.junit.jupiter.engine.descriptor;
1212

13+
import static java.util.Collections.emptyList;
14+
1315
import java.lang.reflect.AnnotatedElement;
1416
import java.lang.reflect.Method;
17+
import java.util.List;
1518
import java.util.Optional;
1619

1720
import org.junit.jupiter.api.TestInstance;
@@ -40,6 +43,11 @@ public Optional<Class<?>> getTestClass() {
4043
return Optional.empty();
4144
}
4245

46+
@Override
47+
public List<Class<?>> getEnclosingTestClasses() {
48+
return emptyList();
49+
}
50+
4351
@Override
4452
public Optional<TestInstance.Lifecycle> getTestInstanceLifecycle() {
4553
return Optional.empty();

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java

+8
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010

1111
package org.junit.jupiter.engine.descriptor;
1212

13+
import static java.util.Collections.emptyList;
14+
1315
import java.lang.reflect.AnnotatedElement;
1416
import java.lang.reflect.Method;
17+
import java.util.List;
1518
import java.util.Optional;
1619

1720
import org.junit.jupiter.api.TestInstance.Lifecycle;
@@ -43,6 +46,11 @@ public Optional<Class<?>> getTestClass() {
4346
return Optional.empty();
4447
}
4548

49+
@Override
50+
public List<Class<?>> getEnclosingTestClasses() {
51+
return emptyList();
52+
}
53+
4654
@Override
4755
public Optional<Lifecycle> getTestInstanceLifecycle() {
4856
return Optional.empty();

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public Function<ResourceLocksProvider, Set<ResourceLocksProvider.Lock>> getResou
106106
getTestMethod()));
107107
}
108108

109-
private List<Class<?>> getEnclosingTestClasses() {
109+
List<Class<?>> getEnclosingTestClasses() {
110110
return getParent() //
111111
.filter(ClassBasedTestDescriptor.class::isInstance) //
112112
.map(ClassBasedTestDescriptor.class::cast) //

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.lang.reflect.AnnotatedElement;
1414
import java.lang.reflect.Method;
15+
import java.util.List;
1516
import java.util.Optional;
1617

1718
import org.junit.jupiter.api.TestInstance.Lifecycle;
@@ -51,6 +52,11 @@ public Optional<Class<?>> getTestClass() {
5152
return Optional.of(getTestDescriptor().getTestClass());
5253
}
5354

55+
@Override
56+
public List<Class<?>> getEnclosingTestClasses() {
57+
return getTestDescriptor().getEnclosingTestClasses();
58+
}
59+
5460
@Override
5561
public Optional<Lifecycle> getTestInstanceLifecycle() {
5662
return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle);

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.lang.reflect.AnnotatedElement;
1414
import java.lang.reflect.Method;
15+
import java.util.List;
1516
import java.util.Optional;
1617

1718
import org.junit.jupiter.api.TestInstance.Lifecycle;
@@ -47,6 +48,11 @@ public Optional<Class<?>> getTestClass() {
4748
return Optional.of(getTestDescriptor().getTestClass());
4849
}
4950

51+
@Override
52+
public List<Class<?>> getEnclosingTestClasses() {
53+
return getTestDescriptor().getEnclosingTestClasses();
54+
}
55+
5056
@Override
5157
public Optional<Lifecycle> getTestInstanceLifecycle() {
5258
return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle);

jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java

+78-21
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.junit.jupiter.api.BeforeEach;
4040
import org.junit.jupiter.api.DisplayNameGenerator;
4141
import org.junit.jupiter.api.Named;
42+
import org.junit.jupiter.api.Nested;
4243
import org.junit.jupiter.api.Tag;
4344
import org.junit.jupiter.api.Test;
4445
import org.junit.jupiter.api.extension.Extension;
@@ -111,33 +112,63 @@ void fromJupiterEngineDescriptor() {
111112
}
112113

113114
@Test
114-
@SuppressWarnings("resource")
115115
void fromClassTestDescriptor() {
116116
var nestedClassDescriptor = nestedClassDescriptor();
117117
var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor);
118+
var doublyNestedClassDescriptor = doublyNestedClassDescriptor();
119+
var methodTestDescriptor = nestedMethodDescriptor();
120+
nestedClassDescriptor.addChild(doublyNestedClassDescriptor);
121+
nestedClassDescriptor.addChild(methodTestDescriptor);
118122

119123
var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, configuration,
120124
extensionRegistry, null);
121125

122126
// @formatter:off
123127
assertAll("outerContext",
124-
() -> assertThat(outerExtensionContext.getElement()).contains(OuterClass.class),
125-
() -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClass.class),
128+
() -> assertThat(outerExtensionContext.getElement()).contains(OuterClassTestCase.class),
129+
() -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClassTestCase.class),
126130
() -> assertThat(outerExtensionContext.getTestInstance()).isEmpty(),
127131
() -> assertThat(outerExtensionContext.getTestMethod()).isEmpty(),
128-
() -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class),
132+
() -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClassTestCase.class),
129133
() -> assertThrows(PreconditionViolationException.class, outerExtensionContext::getRequiredTestInstance),
130134
() -> assertThrows(PreconditionViolationException.class, outerExtensionContext::getRequiredTestMethod),
131135
() -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()),
132136
() -> assertThat(outerExtensionContext.getParent()).isEmpty(),
133137
() -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD),
134-
() -> assertThat(outerExtensionContext.getExtensions(PreInterruptCallback.class)).isEmpty()
138+
() -> assertThat(outerExtensionContext.getExtensions(PreInterruptCallback.class)).isEmpty(),
139+
() -> assertThat(outerExtensionContext.getEnclosingTestClasses()).isEmpty()
135140
);
136141
// @formatter:on
137142

138143
var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor,
139144
configuration, extensionRegistry, null);
140-
assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext);
145+
// @formatter:off
146+
assertAll("nestedContext",
147+
() -> assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext),
148+
() -> assertThat(nestedExtensionContext.getTestClass()).contains(OuterClassTestCase.NestedClass.class),
149+
() -> assertThat(nestedExtensionContext.getEnclosingTestClasses()).containsExactly(OuterClassTestCase.class)
150+
);
151+
// @formatter:on
152+
153+
var doublyNestedExtensionContext = new ClassExtensionContext(nestedExtensionContext, null,
154+
doublyNestedClassDescriptor, configuration, extensionRegistry, null);
155+
// @formatter:off
156+
assertAll("doublyNestedContext",
157+
() -> assertThat(doublyNestedExtensionContext.getParent()).containsSame(nestedExtensionContext),
158+
() -> assertThat(doublyNestedExtensionContext.getTestClass()).contains(OuterClassTestCase.NestedClass.DoublyNestedClass.class),
159+
() -> assertThat(doublyNestedExtensionContext.getEnclosingTestClasses()).containsExactly(OuterClassTestCase.class, OuterClassTestCase.NestedClass.class)
160+
);
161+
// @formatter:on
162+
163+
var methodExtensionContext = new MethodExtensionContext(nestedExtensionContext, null, methodTestDescriptor,
164+
configuration, extensionRegistry, new OpenTest4JAwareThrowableCollector());
165+
// @formatter:off
166+
assertAll("methodContext",
167+
() -> assertThat(methodExtensionContext.getParent()).containsSame(nestedExtensionContext),
168+
() -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClassTestCase.NestedClass.class),
169+
() -> assertThat(methodExtensionContext.getEnclosingTestClasses()).containsExactly(OuterClassTestCase.class)
170+
);
171+
// @formatter:on
141172
}
142173

143174
@Test
@@ -154,7 +185,6 @@ void ExtensionContext_With_ExtensionRegistry_getExtensions() {
154185
}
155186

156187
@Test
157-
@SuppressWarnings("resource")
158188
void tagsCanBeRetrievedInExtensionContext() {
159189
var nestedClassDescriptor = nestedClassDescriptor();
160190
var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor);
@@ -174,20 +204,19 @@ void tagsCanBeRetrievedInExtensionContext() {
174204

175205
var methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, methodTestDescriptor,
176206
configuration, extensionRegistry, new OpenTest4JAwareThrowableCollector());
177-
methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass()));
207+
methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClassTestCase()));
178208
assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag");
179209
assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext);
180210
}
181211

182212
@Test
183-
@SuppressWarnings("resource")
184213
void fromMethodTestDescriptor() {
185214
var methodTestDescriptor = methodDescriptor();
186215
var classTestDescriptor = outerClassDescriptor(methodTestDescriptor);
187216
var engineDescriptor = new JupiterEngineDescriptor(UniqueId.forEngine("junit-jupiter"), configuration);
188217
engineDescriptor.addChild(classTestDescriptor);
189218

190-
Object testInstance = new OuterClass();
219+
Object testInstance = new OuterClassTestCase();
191220
var testMethod = methodTestDescriptor.getTestMethod();
192221

193222
var engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, configuration,
@@ -201,10 +230,11 @@ void fromMethodTestDescriptor() {
201230
// @formatter:off
202231
assertAll("methodContext",
203232
() -> assertThat(methodExtensionContext.getElement()).contains(testMethod),
204-
() -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClass.class),
233+
() -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClassTestCase.class),
234+
() -> assertThat(methodExtensionContext.getEnclosingTestClasses()).isEmpty(),
205235
() -> assertThat(methodExtensionContext.getTestInstance()).contains(testInstance),
206236
() -> assertThat(methodExtensionContext.getTestMethod()).contains(testMethod),
207-
() -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class),
237+
() -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClassTestCase.class),
208238
() -> assertThat(methodExtensionContext.getRequiredTestInstance()).isEqualTo(testInstance),
209239
() -> assertThat(methodExtensionContext.getRequiredTestMethod()).isEqualTo(testMethod),
210240
() -> assertThat(methodExtensionContext.getDisplayName()).isEqualTo(methodTestDescriptor.getDisplayName()),
@@ -359,7 +389,7 @@ void usingStore() {
359389
extensionRegistry, null);
360390
var childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, configuration,
361391
extensionRegistry, new OpenTest4JAwareThrowableCollector());
362-
childContext.setTestInstances(DefaultTestInstances.of(new OuterClass()));
392+
childContext.setTestInstances(DefaultTestInstances.of(new OuterClassTestCase()));
363393

364394
var childStore = childContext.getStore(Namespace.GLOBAL);
365395
var parentStore = parentContext.getStore(Namespace.GLOBAL);
@@ -430,13 +460,18 @@ void configurationParameter(Function<JupiterConfiguration, ? extends ExtensionCo
430460
}
431461

432462
private NestedClassTestDescriptor nestedClassDescriptor() {
433-
return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClass.NestedClass.class,
434-
List::of, configuration);
463+
return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"),
464+
OuterClassTestCase.NestedClass.class, List::of, configuration);
465+
}
466+
467+
private NestedClassTestDescriptor doublyNestedClassDescriptor() {
468+
return new NestedClassTestDescriptor(UniqueId.root("nested-class", "DoublyNestedClass"),
469+
OuterClassTestCase.NestedClass.DoublyNestedClass.class, List::of, configuration);
435470
}
436471

437472
private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) {
438-
var classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), OuterClass.class,
439-
configuration);
473+
var classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"),
474+
OuterClassTestCase.class, configuration);
440475
if (child != null) {
441476
classTestDescriptor.addChild(child);
442477
}
@@ -445,19 +480,41 @@ private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) {
445480

446481
private TestMethodTestDescriptor methodDescriptor() {
447482
try {
448-
return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClass.class,
449-
OuterClass.class.getDeclaredMethod("aMethod"), List::of, configuration);
483+
return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClassTestCase.class,
484+
OuterClassTestCase.class.getDeclaredMethod("aMethod"), List::of, configuration);
450485
}
451486
catch (NoSuchMethodException e) {
452487
throw new RuntimeException(e);
453488
}
454489
}
455490

491+
private TestMethodTestDescriptor nestedMethodDescriptor() {
492+
try {
493+
return new TestMethodTestDescriptor(UniqueId.root("method", "nestedMethod"),
494+
OuterClassTestCase.NestedClass.class, BaseNestedTestCase.class.getDeclaredMethod("nestedMethod"),
495+
List::of, configuration);
496+
}
497+
catch (NoSuchMethodException e) {
498+
throw new RuntimeException(e);
499+
}
500+
}
501+
502+
static abstract class BaseNestedTestCase {
503+
@Test
504+
void nestedMethod() {
505+
}
506+
507+
@Nested
508+
class DoublyNestedClass {
509+
}
510+
}
511+
456512
@Tag("outer-tag")
457-
static class OuterClass {
513+
static class OuterClassTestCase {
458514

459515
@Tag("nested-tag")
460-
static class NestedClass {
516+
@Nested
517+
class NestedClass extends BaseNestedTestCase {
461518
}
462519

463520
@Tag("method-tag")

jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.reflect.Constructor;
2424
import java.lang.reflect.Method;
2525
import java.nio.file.Path;
26+
import java.util.List;
2627
import java.util.Map;
2728
import java.util.Optional;
2829
import java.util.Set;
@@ -251,6 +252,11 @@ public Optional<Class<?>> getTestClass() {
251252
return Optional.empty();
252253
}
253254

255+
@Override
256+
public List<Class<?>> getEnclosingTestClasses() {
257+
return List.of();
258+
}
259+
254260
@Override
255261
public Optional<Lifecycle> getTestInstanceLifecycle() {
256262
return Optional.empty();

0 commit comments

Comments
 (0)