0
# Extension Framework
1
2
Comprehensive extension system for custom test behavior, dependency injection, lifecycle callbacks, and test monitoring. The extension framework allows developers to extend JUnit Jupiter's functionality through well-defined extension points and provides a robust context system for sharing data between extensions and tests.
3
4
## Capabilities
5
6
### Extension Registration
7
8
Register extensions declaratively or programmatically to extend test functionality.
9
10
```java { .api }
11
/**
12
* Declarative extension registration - registers extension class
13
*/
14
@ExtendWith(MyExtension.class)
15
@ExtendWith({TimingExtension.class, LoggingExtension.class})
16
17
/**
18
* Programmatic extension registration - registers extension instance
19
* Must be applied to static fields
20
*/
21
@RegisterExtension
22
static final MyExtension extension = new MyExtension("config");
23
24
@RegisterExtension
25
static final TemporaryFolder tempFolder = new TemporaryFolder();
26
```
27
28
**Usage Examples:**
29
30
```java
31
// Declarative registration at class level
32
@ExtendWith({DatabaseExtension.class, MockitoExtension.class})
33
class UserServiceTest {
34
35
// Programmatic registration with configuration
36
@RegisterExtension
37
static final TimerExtension timer = new TimerExtension()
38
.withTimeout(Duration.ofSeconds(10))
39
.withLogging(true);
40
41
@Test
42
void testUserCreation() {
43
// Extensions are automatically applied
44
UserService service = new UserService();
45
User user = service.createUser("john", "john@example.com");
46
assertNotNull(user);
47
}
48
}
49
50
// Method-level registration
51
class SpecificTest {
52
53
@Test
54
@ExtendWith(RetryExtension.class)
55
void flakyNetworkTest() {
56
// Only this test uses retry extension
57
NetworkService.connect();
58
}
59
}
60
```
61
62
### Core Extension Interface
63
64
Base interface that all extensions must implement.
65
66
```java { .api }
67
/**
68
* Marker interface for all JUnit Jupiter extensions
69
*/
70
interface Extension {
71
// Marker interface - no methods to implement
72
}
73
```
74
75
### Lifecycle Callback Extensions
76
77
Extensions that hook into test execution lifecycle at various points.
78
79
```java { .api }
80
/**
81
* Executed before all tests in a container
82
*/
83
interface BeforeAllCallback extends Extension {
84
void beforeAll(ExtensionContext context) throws Exception;
85
}
86
87
/**
88
* Executed after all tests in a container
89
*/
90
interface AfterAllCallback extends Extension {
91
void afterAll(ExtensionContext context) throws Exception;
92
}
93
94
/**
95
* Executed before each test method
96
*/
97
interface BeforeEachCallback extends Extension {
98
void beforeEach(ExtensionContext context) throws Exception;
99
}
100
101
/**
102
* Executed after each test method
103
*/
104
interface AfterEachCallback extends Extension {
105
void afterEach(ExtensionContext context) throws Exception;
106
}
107
108
/**
109
* Executed immediately before test method execution
110
*/
111
interface BeforeTestExecutionCallback extends Extension {
112
void beforeTestExecution(ExtensionContext context) throws Exception;
113
}
114
115
/**
116
* Executed immediately after test method execution
117
*/
118
interface AfterTestExecutionCallback extends Extension {
119
void afterTestExecution(ExtensionContext context) throws Exception;
120
}
121
```
122
123
**Usage Examples:**
124
125
```java
126
// Database setup/teardown extension
127
public class DatabaseExtension implements BeforeAllCallback, AfterAllCallback,
128
BeforeEachCallback, AfterEachCallback {
129
130
private static Database database;
131
132
@Override
133
public void beforeAll(ExtensionContext context) throws Exception {
134
database = Database.create();
135
database.start();
136
context.getStore(NAMESPACE).put("database", database);
137
}
138
139
@Override
140
public void beforeEach(ExtensionContext context) throws Exception {
141
database.beginTransaction();
142
}
143
144
@Override
145
public void afterEach(ExtensionContext context) throws Exception {
146
database.rollback();
147
}
148
149
@Override
150
public void afterAll(ExtensionContext context) throws Exception {
151
database.stop();
152
}
153
154
private static final Namespace NAMESPACE = Namespace.create("database");
155
}
156
157
// Timing extension
158
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
159
160
@Override
161
public void beforeTestExecution(ExtensionContext context) throws Exception {
162
long startTime = System.currentTimeMillis();
163
context.getStore(NAMESPACE).put("startTime", startTime);
164
}
165
166
@Override
167
public void afterTestExecution(ExtensionContext context) throws Exception {
168
long startTime = context.getStore(NAMESPACE).get("startTime", Long.class);
169
long duration = System.currentTimeMillis() - startTime;
170
System.out.printf("Test %s took %d ms%n", context.getDisplayName(), duration);
171
}
172
173
private static final Namespace NAMESPACE = Namespace.create("timing");
174
}
175
```
176
177
### Parameter Resolution
178
179
Extensions for dependency injection into test constructors and methods.
180
181
```java { .api }
182
/**
183
* Resolves parameters for test constructors and methods
184
*/
185
interface ParameterResolver extends Extension {
186
/**
187
* Determine if this resolver supports the given parameter
188
*/
189
boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
190
throws org.junit.jupiter.api.extension.ParameterResolutionException;
191
192
/**
193
* Resolve the parameter value
194
*/
195
Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
196
throws org.junit.jupiter.api.extension.ParameterResolutionException;
197
}
198
199
/**
200
* Context information about the parameter being resolved
201
*/
202
interface ParameterContext {
203
java.lang.reflect.Parameter getParameter();
204
int getIndex();
205
java.util.Optional<Object> getTarget();
206
}
207
```
208
209
**Usage Examples:**
210
211
```java
212
// Custom parameter resolver for injecting test data
213
public class TestDataResolver implements ParameterResolver {
214
215
@Override
216
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
217
return parameterContext.getParameter().getType() == TestData.class;
218
}
219
220
@Override
221
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
222
String testName = extensionContext.getDisplayName();
223
return TestDataFactory.createFor(testName);
224
}
225
}
226
227
// Usage in test
228
@ExtendWith(TestDataResolver.class)
229
class ParameterizedTest {
230
231
@Test
232
void testWithInjectedData(TestData data) {
233
// TestData automatically injected by resolver
234
assertNotNull(data);
235
assertTrue(data.isValid());
236
}
237
238
@Test
239
void testWithMultipleParameters(TestData data, TestInfo testInfo) {
240
// Mix custom and built-in parameter resolvers
241
assertEquals(testInfo.getDisplayName(), data.getTestName());
242
}
243
}
244
245
// Built-in parameter resolvers inject these types automatically:
246
@Test
247
void testWithBuiltInParameters(TestInfo testInfo, TestReporter testReporter,
248
RepetitionInfo repetitionInfo) {
249
testReporter.publishEntry("test", testInfo.getDisplayName());
250
if (repetitionInfo != null) {
251
testReporter.publishEntry("repetition", String.valueOf(repetitionInfo.getCurrentRepetition()));
252
}
253
}
254
```
255
256
### Test Instance Management
257
258
Extensions for controlling test instance creation and lifecycle.
259
260
```java { .api }
261
/**
262
* Custom test instance creation
263
*/
264
interface TestInstanceFactory extends Extension {
265
Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext)
266
throws org.junit.jupiter.api.extension.TestInstantiationException;
267
}
268
269
/**
270
* Post-process test instances after creation
271
*/
272
interface TestInstancePostProcessor extends Extension {
273
void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception;
274
}
275
276
/**
277
* Called before test instance construction
278
* Provides access to test-specific data when using TEST_METHOD scope
279
*/
280
interface TestInstancePreConstructCallback extends TestInstantiationAwareExtension {
281
/**
282
* Callback invoked prior to test instances being constructed
283
* @param factoryContext the context for the test instance about to be instantiated
284
* @param context the current extension context
285
*/
286
void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception;
287
}
288
289
/**
290
* Called before test instance destruction
291
* Handles cleanup of resources created for test instances
292
*/
293
interface TestInstancePreDestroyCallback extends Extension {
294
/**
295
* Callback for processing test instances before they are destroyed
296
* Called once per ExtensionContext even for nested tests
297
* @param context the current extension context
298
*/
299
void preDestroyTestInstance(ExtensionContext context) throws Exception;
300
301
/**
302
* Utility method for processing all test instances about to be destroyed
303
* Ensures correct handling regardless of lifecycle mode
304
* @param context the current extension context
305
* @param callback invoked for every test instance about to be destroyed
306
*/
307
static void preDestroyTestInstances(ExtensionContext context, java.util.function.Consumer<Object> callback);
308
}
309
310
/**
311
* Base interface for extensions aware of test instantiation
312
* Allows control over ExtensionContext scope during instantiation
313
*/
314
interface TestInstantiationAwareExtension extends Extension {
315
/**
316
* Determines extension context scope during test instantiation
317
* @param rootContext the root extension context for configuration access
318
* @return the desired extension context scope
319
*/
320
default ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
321
return ExtensionContextScope.DEFAULT;
322
}
323
324
/**
325
* Extension context scope options for test instantiation
326
*/
327
enum ExtensionContextScope {
328
DEFAULT, // Legacy behavior - class-scoped context
329
TEST_METHOD // Test-method-scoped context (recommended)
330
}
331
}
332
333
/**
334
* Context for test instance factory
335
*/
336
interface TestInstanceFactoryContext {
337
Class<?> getTestClass();
338
java.util.Optional<Object> getOuterInstance();
339
}
340
```
341
342
**Usage Examples:**
343
344
```java
345
// Custom test instance factory with dependency injection
346
public class DITestInstanceFactory implements TestInstanceFactory {
347
348
private final DIContainer container;
349
350
public DITestInstanceFactory(DIContainer container) {
351
this.container = container;
352
}
353
354
@Override
355
public Object createTestInstance(TestInstanceFactoryContext factoryContext,
356
ExtensionContext extensionContext) throws TestInstantiationException {
357
Class<?> testClass = factoryContext.getTestClass();
358
try {
359
return container.createInstance(testClass);
360
} catch (Exception e) {
361
throw new org.junit.jupiter.api.extension.TestInstantiationException("Failed to create test instance", e);
362
}
363
}
364
}
365
366
// Post-processor for field injection
367
public class FieldInjectionExtension implements TestInstancePostProcessor {
368
369
@Override
370
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
371
Class<?> testClass = testInstance.getClass();
372
373
for (java.lang.reflect.Field field : testClass.getDeclaredFields()) {
374
if (field.isAnnotationPresent(Inject.class)) {
375
field.setAccessible(true);
376
Object dependency = resolveDependency(field.getType());
377
field.set(testInstance, dependency);
378
}
379
}
380
}
381
382
private Object resolveDependency(Class<?> type) {
383
// Dependency resolution logic
384
return DIContainer.getInstance().resolve(type);
385
}
386
}
387
388
// Test instance lifecycle management extension
389
public class InstanceLifecycleExtension implements TestInstancePreConstructCallback,
390
TestInstancePreDestroyCallback {
391
392
@Override
393
public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
394
// Use test-method scope for better isolation
395
return ExtensionContextScope.TEST_METHOD;
396
}
397
398
@Override
399
public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception {
400
Class<?> testClass = factoryContext.getTestClass();
401
System.out.println("Preparing to construct test instance: " + testClass.getSimpleName());
402
403
// Initialize test-specific resources before construction
404
String testName = context.getTestMethod().map(method -> method.getName()).orElse("class-level");
405
TestResources resources = TestResources.create(testName);
406
context.getStore(NAMESPACE).put("test.resources", resources);
407
}
408
409
@Override
410
public void preDestroyTestInstance(ExtensionContext context) throws Exception {
411
// Clean up all test instances using the utility method
412
TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> {
413
System.out.println("Cleaning up test instance: " + testInstance.getClass().getSimpleName());
414
415
// Release any resources associated with this instance
416
if (testInstance instanceof ResourceHolder) {
417
((ResourceHolder) testInstance).releaseResources();
418
}
419
});
420
421
// Clean up shared resources from store
422
TestResources resources = context.getStore(NAMESPACE).get("test.resources", TestResources.class);
423
if (resources != null) {
424
resources.cleanup();
425
}
426
}
427
428
private static final Namespace NAMESPACE = Namespace.create("instance", "lifecycle");
429
}
430
```
431
432
### Conditional Execution
433
434
Extensions for runtime test execution decisions.
435
436
```java { .api }
437
/**
438
* Determine whether a test should be executed
439
*/
440
interface ExecutionCondition extends Extension {
441
ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);
442
}
443
444
/**
445
* Result of condition evaluation
446
*/
447
class ConditionEvaluationResult {
448
static ConditionEvaluationResult enabled(String reason);
449
static ConditionEvaluationResult disabled(String reason);
450
451
boolean isDisabled();
452
java.util.Optional<String> getReason();
453
}
454
```
455
456
**Usage Examples:**
457
458
```java
459
// Custom execution condition
460
public class DatabaseAvailableCondition implements ExecutionCondition {
461
462
@Override
463
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
464
if (isDatabaseAvailable()) {
465
return ConditionEvaluationResult.enabled("Database is available");
466
} else {
467
return ConditionEvaluationResult.disabled("Database is not available");
468
}
469
}
470
471
private boolean isDatabaseAvailable() {
472
try {
473
java.sql.Connection conn = java.sql.DriverManager.getConnection("jdbc:h2:mem:test");
474
conn.close();
475
return true;
476
} catch (java.sql.SQLException e) {
477
return false;
478
}
479
}
480
}
481
482
@ExtendWith(DatabaseAvailableCondition.class)
483
class DatabaseTest {
484
485
@Test
486
void testDatabaseOperation() {
487
// Only runs if database is available
488
assertTrue(true);
489
}
490
}
491
```
492
493
### Exception Handling
494
495
Extensions for handling exceptions during test execution.
496
497
```java { .api }
498
/**
499
* Handle exceptions thrown during test execution
500
*/
501
interface TestExecutionExceptionHandler extends Extension {
502
void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
503
}
504
505
/**
506
* Handle exceptions thrown during lifecycle method execution
507
*/
508
interface LifecycleMethodExecutionExceptionHandler extends Extension {
509
void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
510
void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
511
void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
512
void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
513
}
514
```
515
516
**Usage Examples:**
517
518
```java
519
// Retry extension for handling flaky tests
520
public class RetryExtension implements TestExecutionExceptionHandler {
521
522
private static final int MAX_RETRIES = 3;
523
524
@Override
525
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
526
Namespace namespace = Namespace.create("retry");
527
Store store = context.getStore(namespace);
528
529
int attempts = store.getOrDefault("attempts", Integer.class, 0);
530
if (attempts < MAX_RETRIES && isRetryableException(throwable)) {
531
store.put("attempts", attempts + 1);
532
System.out.printf("Retrying test %s (attempt %d/%d)%n",
533
context.getDisplayName(), attempts + 1, MAX_RETRIES);
534
// Re-throw as TestAbortedException to retry
535
throw new TestAbortedException("Retrying due to: " + throwable.getMessage());
536
} else {
537
// Max retries exceeded or non-retryable exception
538
throw throwable;
539
}
540
}
541
542
private boolean isRetryableException(Throwable throwable) {
543
return throwable instanceof java.io.IOException ||
544
throwable instanceof java.net.ConnectException;
545
}
546
}
547
```
548
549
### Test Watching
550
551
Extensions for observing test execution results.
552
553
```java { .api }
554
/**
555
* Observe test execution outcomes
556
*/
557
interface TestWatcher extends Extension {
558
/**
559
* Called when a test is disabled
560
*/
561
default void testDisabled(ExtensionContext context, java.util.Optional<String> reason) {}
562
563
/**
564
* Called when a test completes successfully
565
*/
566
default void testSuccessful(ExtensionContext context) {}
567
568
/**
569
* Called when a test is aborted
570
*/
571
default void testAborted(ExtensionContext context, Throwable cause) {}
572
573
/**
574
* Called when a test fails
575
*/
576
default void testFailed(ExtensionContext context, Throwable cause) {}
577
}
578
```
579
580
**Usage Examples:**
581
582
```java
583
// Test result reporter extension
584
public class TestResultReporter implements TestWatcher {
585
586
private final List<TestResult> results = new ArrayList<>();
587
588
@Override
589
public void testSuccessful(ExtensionContext context) {
590
results.add(new TestResult(context.getDisplayName(), "PASSED", null));
591
System.out.println("✓ " + context.getDisplayName());
592
}
593
594
@Override
595
public void testFailed(ExtensionContext context, Throwable cause) {
596
results.add(new TestResult(context.getDisplayName(), "FAILED", cause.getMessage()));
597
System.out.println("✗ " + context.getDisplayName() + ": " + cause.getMessage());
598
}
599
600
@Override
601
public void testAborted(ExtensionContext context, Throwable cause) {
602
results.add(new TestResult(context.getDisplayName(), "ABORTED", cause.getMessage()));
603
System.out.println("○ " + context.getDisplayName() + ": " + cause.getMessage());
604
}
605
606
@Override
607
public void testDisabled(ExtensionContext context, java.util.Optional<String> reason) {
608
String reasonText = reason.orElse("No reason provided");
609
results.add(new TestResult(context.getDisplayName(), "DISABLED", reasonText));
610
System.out.println("- " + context.getDisplayName() + ": " + reasonText);
611
}
612
613
public java.util.List<TestResult> getResults() {
614
return java.util.Collections.unmodifiableList(results);
615
}
616
}
617
```
618
619
### Method Invocation Interception
620
621
Extensions for intercepting and modifying test method invocations.
622
623
```java { .api }
624
/**
625
* Intercept test method invocations
626
*/
627
interface InvocationInterceptor extends Extension {
628
/**
629
* Intercept test method invocation
630
*/
631
default void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,
632
ExtensionContext extensionContext) throws Throwable {
633
invocation.proceed();
634
}
635
636
/**
637
* Intercept test template method invocation
638
*/
639
default <T> T interceptTestTemplateMethod(Invocation<T> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,
640
ExtensionContext extensionContext) throws Throwable {
641
return invocation.proceed();
642
}
643
644
/**
645
* Intercept test factory method invocation
646
*/
647
default <T> T interceptTestFactoryMethod(Invocation<T> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,
648
ExtensionContext extensionContext) throws Throwable {
649
return invocation.proceed();
650
}
651
652
/**
653
* Intercept dynamic test invocation
654
*/
655
default void interceptDynamicTest(Invocation<Void> invocation, DynamicTestInvocationContext invocationContext,
656
ExtensionContext extensionContext) throws Throwable {
657
invocation.proceed();
658
}
659
}
660
```
661
662
### Extension Context
663
664
Comprehensive context information and utilities available to all extensions.
665
666
```java { .api }
667
/**
668
* Context information for extension execution
669
*/
670
interface ExtensionContext {
671
/**
672
* Get the unique ID of this context
673
*/
674
String getUniqueId();
675
676
/**
677
* Get the display name
678
*/
679
String getDisplayName();
680
681
/**
682
* Get all tags associated with this context
683
*/
684
java.util.Set<String> getTags();
685
686
/**
687
* Get the parent context (if any)
688
*/
689
java.util.Optional<ExtensionContext> getParent();
690
691
/**
692
* Get the root context
693
*/
694
ExtensionContext getRoot();
695
696
/**
697
* Get the test element (class, method, etc.)
698
*/
699
java.util.Optional<java.lang.reflect.AnnotatedElement> getElement();
700
701
/**
702
* Get the test class
703
*/
704
java.util.Optional<Class<?>> getTestClass();
705
706
/**
707
* Get the test method (if this is a method context)
708
*/
709
java.util.Optional<java.lang.reflect.Method> getTestMethod();
710
711
/**
712
* Get the test instance (if available)
713
*/
714
java.util.Optional<Object> getTestInstance();
715
716
/**
717
* Get all test instances in the hierarchy
718
*/
719
java.util.Optional<TestInstances> getTestInstances();
720
721
/**
722
* Get a namespace-scoped data store
723
*/
724
Store getStore(Namespace namespace);
725
726
/**
727
* Publish a report entry
728
*/
729
void publishReportEntry(java.util.Map<String, String> map);
730
void publishReportEntry(String key, String value);
731
732
/**
733
* Get configuration parameter value
734
*/
735
java.util.Optional<String> getConfigurationParameter(String key);
736
737
/**
738
* Get configuration parameter with type conversion
739
*/
740
<T> java.util.Optional<T> getConfigurationParameter(String key, java.util.function.Function<String, T> transformer);
741
742
/**
743
* Get the ExecutableInvoker for this context
744
* Allows dynamic invocation of methods and constructors with parameter resolution
745
*/
746
ExecutableInvoker getExecutableInvoker();
747
}
748
```
749
750
### Timeout Handling Extensions
751
752
Extensions for handling timeouts and thread interrupts.
753
754
```java { .api }
755
/**
756
* Called before thread interruption during timeout handling
757
*/
758
interface PreInterruptCallback extends Extension {
759
/**
760
* Property name for enabling thread dump on timeout
761
*/
762
String THREAD_DUMP_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.timeout.threaddump.enabled";
763
764
/**
765
* Callback invoked before a thread is interrupted
766
* @param preInterruptContext context with target thread information
767
* @param extensionContext the extension context for the callback
768
*/
769
void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception;
770
}
771
772
/**
773
* Context information for thread interruption
774
*/
775
interface PreInterruptContext {
776
/**
777
* Get the thread that will be interrupted
778
* @return the target thread
779
*/
780
Thread getThreadToInterrupt();
781
}
782
```
783
784
**Usage Examples:**
785
786
```java
787
// Thread dump extension for timeout debugging
788
public class TimeoutDebuggingExtension implements PreInterruptCallback {
789
790
@Override
791
public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception {
792
Thread targetThread = preInterruptContext.getThreadToInterrupt();
793
System.err.printf("About to interrupt thread '%s' due to timeout in %s%n",
794
targetThread.getName(), extensionContext.getDisplayName());
795
796
// Dump thread stack for debugging
797
StackTraceElement[] stack = targetThread.getStackTrace();
798
System.err.println("Thread stack trace:");
799
for (StackTraceElement element : stack) {
800
System.err.println(" at " + element);
801
}
802
803
// Log additional context information
804
extensionContext.publishReportEntry("timeout.thread", targetThread.getName());
805
extensionContext.publishReportEntry("timeout.test", extensionContext.getDisplayName());
806
}
807
}
808
809
// Usage
810
@ExtendWith(TimeoutDebuggingExtension.class)
811
class TimeoutTest {
812
813
@Test
814
@Timeout(value = 1, unit = TimeUnit.SECONDS)
815
void testThatMightTimeout() {
816
// Test logic that might exceed timeout
817
while (true) {
818
// Infinite loop to demonstrate timeout
819
}
820
}
821
}
822
```
823
824
### Executable Invocation
825
826
Utility for invoking methods and constructors with parameter resolution.
827
828
```java { .api }
829
/**
830
* Allows invoking executables with dynamic parameter resolution
831
*/
832
interface ExecutableInvoker {
833
/**
834
* Invoke static method with parameter resolution
835
* @param method the method to invoke
836
* @return method result
837
*/
838
default Object invoke(java.lang.reflect.Method method) {
839
return invoke(method, null);
840
}
841
842
/**
843
* Invoke method with parameter resolution
844
* @param method the method to invoke
845
* @param target the target instance (null for static methods)
846
* @return method result
847
*/
848
Object invoke(java.lang.reflect.Method method, Object target);
849
850
/**
851
* Invoke constructor with parameter resolution
852
* @param constructor the constructor to invoke
853
* @return constructed instance
854
*/
855
default <T> T invoke(java.lang.reflect.Constructor<T> constructor) {
856
return invoke(constructor, null);
857
}
858
859
/**
860
* Invoke constructor with outer instance and parameter resolution
861
* @param constructor the constructor to invoke
862
* @param outerInstance the outer instance for inner classes
863
* @return constructed instance
864
*/
865
<T> T invoke(java.lang.reflect.Constructor<T> constructor, Object outerInstance);
866
}
867
```
868
869
**Usage Examples:**
870
871
```java
872
// Extension that demonstrates ExecutableInvoker usage
873
public class DynamicInvocationExtension implements BeforeEachCallback, ParameterResolver {
874
875
@Override
876
public void beforeEach(ExtensionContext context) throws Exception {
877
// Get the ExecutableInvoker from the context
878
ExecutableInvoker invoker = context.getExecutableInvoker();
879
880
// Dynamically invoke setup methods with parameter resolution
881
java.lang.reflect.Method[] methods = context.getRequiredTestClass().getDeclaredMethods();
882
for (java.lang.reflect.Method method : methods) {
883
if (method.isAnnotationPresent(DynamicSetup.class)) {
884
Object testInstance = context.getRequiredTestInstance();
885
invoker.invoke(method, testInstance);
886
}
887
}
888
}
889
890
@Override
891
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
892
return parameterContext.getParameter().getType() == ExecutableInvoker.class;
893
}
894
895
@Override
896
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
897
return extensionContext.getExecutableInvoker();
898
}
899
}
900
901
// Usage in tests
902
@ExtendWith(DynamicInvocationExtension.class)
903
class DynamicInvocationTest {
904
905
@DynamicSetup
906
void setupWithParameters(TestInfo testInfo, TestReporter reporter) {
907
reporter.publishEntry("setup", "Dynamic setup called for " + testInfo.getDisplayName());
908
}
909
910
@Test
911
void testWithInvoker(ExecutableInvoker invoker) throws Exception {
912
// Use invoker to dynamically call methods
913
java.lang.reflect.Method method = this.getClass().getDeclaredMethod("helperMethod", String.class);
914
Object result = invoker.invoke(method, this);
915
assertNotNull(result);
916
}
917
918
String helperMethod(String input) {
919
return "Helper: " + input;
920
}
921
}
922
```
923
924
### Extension Container Annotations
925
926
Annotations for organizing extension declarations.
927
928
```java { .api }
929
/**
930
* Container for multiple @ExtendWith declarations
931
* Optional since @ExtendWith is repeatable
932
*/
933
@Extensions({
934
@ExtendWith(DatabaseExtension.class),
935
@ExtendWith(MockitoExtension.class),
936
@ExtendWith(TimingExtension.class)
937
})
938
@interface Extensions {
939
ExtendWith[] value();
940
}
941
```
942
943
**Usage Examples:**
944
945
```java
946
// Using @Extensions container (optional)
947
@Extensions({
948
@ExtendWith(DatabaseExtension.class),
949
@ExtendWith(LoggingExtension.class),
950
@ExtendWith(MetricsExtension.class)
951
})
952
class MultiExtensionTest {
953
954
@Test
955
void testWithMultipleExtensions() {
956
// All extensions are applied
957
assertTrue(true);
958
}
959
}
960
961
// Alternative using repeatable @ExtendWith (recommended)
962
@ExtendWith(DatabaseExtension.class)
963
@ExtendWith(LoggingExtension.class)
964
@ExtendWith(MetricsExtension.class)
965
class RepeatedExtensionTest {
966
967
@Test
968
void testWithRepeatedExtensions() {
969
// Same effect as @Extensions container
970
assertTrue(true);
971
}
972
}
973
```
974
975
### Extension Context Store
976
977
Scoped data storage for sharing information between extensions and tests.
978
979
```java { .api }
980
/**
981
* Namespace-scoped key-value store for extension data
982
*/
983
interface Store {
984
/**
985
* Get value by key
986
*/
987
Object get(Object key);
988
989
/**
990
* Get typed value by key
991
*/
992
<V> V get(Object key, Class<V> requiredType);
993
994
/**
995
* Get value with default
996
*/
997
<K, V> Object getOrDefault(K key, Class<V> requiredType, V defaultValue);
998
999
/**
1000
* Put key-value pair
1001
*/
1002
void put(Object key, Object value);
1003
1004
/**
1005
* Get or compute value if absent
1006
*/
1007
Object getOrComputeIfAbsent(Object key, java.util.function.Function<Object, Object> defaultCreator);
1008
1009
/**
1010
* Get or compute typed value if absent
1011
*/
1012
<K, V> V getOrComputeIfAbsent(K key, java.util.function.Function<K, V> defaultCreator, Class<V> requiredType);
1013
1014
/**
1015
* Remove value by key
1016
*/
1017
Object remove(Object key);
1018
1019
/**
1020
* Remove typed value by key
1021
*/
1022
<V> V remove(Object key, Class<V> requiredType);
1023
}
1024
1025
/**
1026
* Namespace for scoping store data
1027
*/
1028
class Namespace {
1029
/**
1030
* Global namespace
1031
*/
1032
static final Namespace GLOBAL = Namespace.create("global");
1033
1034
/**
1035
* Create namespace from parts
1036
*/
1037
static Namespace create(Object... parts);
1038
}
1039
```
1040
1041
**Usage Examples:**
1042
1043
```java
1044
public class SharedDataExtension implements BeforeAllCallback, BeforeEachCallback {
1045
1046
private static final Namespace NAMESPACE = Namespace.create("shared", "data");
1047
1048
@Override
1049
public void beforeAll(ExtensionContext context) throws Exception {
1050
// Store class-level shared data
1051
ExpensiveResource resource = new ExpensiveResource();
1052
context.getStore(NAMESPACE).put("resource", resource);
1053
}
1054
1055
@Override
1056
public void beforeEach(ExtensionContext context) throws Exception {
1057
// Access shared data and create per-test data
1058
ExpensiveResource resource = context.getStore(NAMESPACE)
1059
.get("resource", ExpensiveResource.class);
1060
1061
TestData testData = resource.createTestData(context.getDisplayName());
1062
context.getStore(NAMESPACE).put("testData", testData);
1063
}
1064
1065
public static TestData getTestData(ExtensionContext context) {
1066
return context.getStore(NAMESPACE).get("testData", TestData.class);
1067
}
1068
}
1069
```
1070
1071
## Exception Types
1072
1073
```java { .api }
1074
/**
1075
* Exception thrown when extension configuration is invalid
1076
*/
1077
class ExtensionConfigurationException extends JUnitException {
1078
ExtensionConfigurationException(String message);
1079
ExtensionConfigurationException(String message, Throwable cause);
1080
}
1081
1082
/**
1083
* Exception thrown when parameter resolution fails
1084
*/
1085
class ParameterResolutionException extends JUnitException {
1086
ParameterResolutionException(String message);
1087
ParameterResolutionException(String message, Throwable cause);
1088
}
1089
1090
/**
1091
* Exception thrown when test instantiation fails
1092
*/
1093
class TestInstantiationException extends JUnitException {
1094
TestInstantiationException(String message);
1095
TestInstantiationException(String message, Throwable cause);
1096
}
1097
1098
/**
1099
* Exception thrown when extension context operations fail
1100
*/
1101
class ExtensionContextException extends JUnitException {
1102
ExtensionContextException(String message);
1103
ExtensionContextException(String message, Throwable cause);
1104
}
1105
```
1106
1107
## Types
1108
1109
### Test Instance Management
1110
1111
```java { .api }
1112
/**
1113
* Container for test instances in nested class hierarchies
1114
*/
1115
class TestInstances {
1116
/**
1117
* Get the innermost (leaf) test instance
1118
*/
1119
Object getInnermostInstance();
1120
1121
/**
1122
* Get all test instances from outermost to innermost
1123
*/
1124
java.util.List<Object> getAllInstances();
1125
1126
/**
1127
* Find instance of specified type
1128
*/
1129
<T> java.util.Optional<T> findInstance(Class<T> requiredType);
1130
}
1131
```
1132
1133
### Extension Support Utilities
1134
1135
```java { .api }
1136
/**
1137
* Base class for type-based parameter resolvers
1138
* @param <T> the type of the parameter supported by this ParameterResolver
1139
*/
1140
abstract class TypeBasedParameterResolver<T> implements ParameterResolver {
1141
1142
/**
1143
* Constructor that automatically discovers the supported parameter type from generic declaration
1144
*/
1145
public TypeBasedParameterResolver();
1146
1147
/**
1148
* Final implementation that checks parameter type against the discovered type
1149
*/
1150
@Override
1151
public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
1152
1153
/**
1154
* Resolve the parameter - must be implemented by subclasses
1155
* @param parameterContext the context for the parameter for which an argument should be resolved
1156
* @param extensionContext the extension context for the method
1157
* @return the resolved parameter object
1158
* @throws ParameterResolutionException if an error occurs resolving the parameter
1159
*/
1160
@Override
1161
public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
1162
throws ParameterResolutionException;
1163
}
1164
```