0
# Extensions and Lifecycle
1
2
JUnit Jupiter's powerful extension model allows customizing test behavior, dependency injection, and integration with external frameworks. Extensions provide hooks into the test lifecycle and enable sophisticated test customizations.
3
4
## Imports
5
6
```java
7
import org.junit.jupiter.api.extension.*;
8
import static org.junit.jupiter.api.Assertions.*;
9
```
10
11
## Capabilities
12
13
### Extension Registration
14
15
Register extensions declaratively or programmatically.
16
17
```java { .api }
18
/**
19
* Register extensions declaratively on test classes and methods
20
*/
21
@Target({ElementType.TYPE, ElementType.METHOD})
22
@Retention(RetentionPolicy.RUNTIME)
23
@Repeatable(ExtendWith.List.class)
24
@interface ExtendWith {
25
/**
26
* Extension classes to register
27
*/
28
Class<? extends Extension>[] value();
29
30
@Target({ElementType.TYPE, ElementType.METHOD})
31
@Retention(RetentionPolicy.RUNTIME)
32
@interface List {
33
ExtendWith[] value();
34
}
35
}
36
37
/**
38
* Register extension instance via static field
39
*/
40
@Target(ElementType.FIELD)
41
@Retention(RetentionPolicy.RUNTIME)
42
@interface RegisterExtension {
43
}
44
45
/**
46
* Base extension interface - marker interface for all extensions
47
*/
48
interface Extension {
49
}
50
```
51
52
**Usage Examples:**
53
54
```java
55
// Declarative registration
56
@ExtendWith(DatabaseExtension.class)
57
@ExtendWith(MockitoExtension.class)
58
class MyTest {
59
60
@Test
61
void testWithExtensions() {
62
// Extensions are active
63
}
64
}
65
66
// Programmatic registration
67
class MyTest {
68
69
@RegisterExtension
70
static DatabaseExtension database = new DatabaseExtension("testdb");
71
72
@RegisterExtension
73
MockServerExtension mockServer = new MockServerExtension(8080);
74
75
@Test
76
void testWithProgrammaticExtensions() {
77
// Extensions configured and active
78
}
79
}
80
```
81
82
### Lifecycle Callback Extensions
83
84
Hook into test lifecycle events.
85
86
```java { .api }
87
/**
88
* Callback before all tests in container
89
*/
90
interface BeforeAllCallback extends Extension {
91
void beforeAll(ExtensionContext context) throws Exception;
92
}
93
94
/**
95
* Callback before each test method
96
*/
97
interface BeforeEachCallback extends Extension {
98
void beforeEach(ExtensionContext context) throws Exception;
99
}
100
101
/**
102
* Callback before test method execution (after @BeforeEach)
103
*/
104
interface BeforeTestExecutionCallback extends Extension {
105
void beforeTestExecution(ExtensionContext context) throws Exception;
106
}
107
108
/**
109
* Callback after test method execution (before @AfterEach)
110
*/
111
interface AfterTestExecutionCallback extends Extension {
112
void afterTestExecution(ExtensionContext context) throws Exception;
113
}
114
115
/**
116
* Callback after each test method
117
*/
118
interface AfterEachCallback extends Extension {
119
void afterEach(ExtensionContext context) throws Exception;
120
}
121
122
/**
123
* Callback after all tests in container
124
*/
125
interface AfterAllCallback extends Extension {
126
void afterAll(ExtensionContext context) throws Exception;
127
}
128
```
129
130
**Usage Example:**
131
132
```java
133
class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
134
135
private static final String START_TIME = "start time";
136
137
@Override
138
public void beforeTestExecution(ExtensionContext context) throws Exception {
139
getStore(context).put(START_TIME, System.currentTimeMillis());
140
}
141
142
@Override
143
public void afterTestExecution(ExtensionContext context) throws Exception {
144
Method testMethod = context.getRequiredTestMethod();
145
long startTime = getStore(context).remove(START_TIME, long.class);
146
long duration = System.currentTimeMillis() - startTime;
147
148
System.out.printf("Method [%s] took %s ms.%n", testMethod.getName(), duration);
149
}
150
151
private ExtensionContext.Store getStore(ExtensionContext context) {
152
return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));
153
}
154
}
155
156
@ExtendWith(TimingExtension.class)
157
class TimedTest {
158
159
@Test
160
void testThatTakesTime() throws InterruptedException {
161
Thread.sleep(100);
162
assertTrue(true);
163
}
164
}
165
```
166
167
### Parameter Resolution Extensions
168
169
Inject custom parameters into test methods and constructors.
170
171
```java { .api }
172
/**
173
* Resolve parameters for test methods and constructors
174
*/
175
interface ParameterResolver extends Extension {
176
/**
177
* Check if this resolver supports the parameter
178
*/
179
boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
180
throws ParameterResolutionException;
181
182
/**
183
* Resolve the parameter value
184
*/
185
Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
186
throws ParameterResolutionException;
187
}
188
189
/**
190
* Parameter context information
191
*/
192
interface ParameterContext {
193
Parameter getParameter();
194
int getIndex();
195
Optional<Object> getTarget();
196
boolean isAnnotated(Class<? extends Annotation> annotationType);
197
<A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType);
198
<A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType);
199
}
200
201
/**
202
* Type-based parameter resolver base class
203
*/
204
abstract class TypeBasedParameterResolver<T> implements ParameterResolver {
205
private final Class<T> parameterType;
206
207
protected TypeBasedParameterResolver(Class<T> parameterType) {
208
this.parameterType = parameterType;
209
}
210
211
@Override
212
public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
213
return parameterType.equals(parameterContext.getParameter().getType());
214
}
215
216
@Override
217
public final Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
218
return resolveParameter(extensionContext);
219
}
220
221
/**
222
* Resolve parameter of the supported type
223
*/
224
public abstract T resolveParameter(ExtensionContext extensionContext);
225
}
226
```
227
228
**Usage Examples:**
229
230
```java
231
class DatabaseConnectionResolver implements ParameterResolver {
232
233
@Override
234
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
235
return parameterContext.getParameter().getType() == Connection.class;
236
}
237
238
@Override
239
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
240
return createDatabaseConnection();
241
}
242
243
private Connection createDatabaseConnection() {
244
// Create and return database connection
245
return DriverManager.getConnection("jdbc:h2:mem:test");
246
}
247
}
248
249
class TempDirectoryResolver extends TypeBasedParameterResolver<Path> {
250
251
public TempDirectoryResolver() {
252
super(Path.class);
253
}
254
255
@Override
256
public Path resolveParameter(ExtensionContext extensionContext) {
257
return Files.createTempDirectory("junit-test");
258
}
259
}
260
261
@ExtendWith({DatabaseConnectionResolver.class, TempDirectoryResolver.class})
262
class DatabaseTest {
263
264
@Test
265
void testWithInjectedParameters(Connection connection, Path tempDir) {
266
assertNotNull(connection);
267
assertNotNull(tempDir);
268
assertTrue(Files.exists(tempDir));
269
}
270
}
271
```
272
273
### Conditional Execution Extensions
274
275
Control when tests should be executed.
276
277
```java { .api }
278
/**
279
* Determine whether test should be executed
280
*/
281
interface ExecutionCondition extends Extension {
282
/**
283
* Evaluate execution condition
284
*/
285
ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);
286
}
287
288
/**
289
* Result of condition evaluation
290
*/
291
class ConditionEvaluationResult {
292
/**
293
* Create enabled result
294
*/
295
static ConditionEvaluationResult enabled(String reason);
296
297
/**
298
* Create disabled result
299
*/
300
static ConditionEvaluationResult disabled(String reason);
301
302
/**
303
* Check if execution is disabled
304
*/
305
boolean isDisabled();
306
307
/**
308
* Get reason for the result
309
*/
310
Optional<String> getReason();
311
}
312
```
313
314
**Usage Example:**
315
316
```java
317
class SystemPropertyCondition implements ExecutionCondition {
318
319
@Override
320
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
321
Optional<SystemProperty> annotation = context.getElement()
322
.map(element -> element.getAnnotation(SystemProperty.class));
323
324
if (annotation.isPresent()) {
325
SystemProperty systemProperty = annotation.get();
326
String actualValue = System.getProperty(systemProperty.name());
327
328
if (systemProperty.value().equals(actualValue)) {
329
return ConditionEvaluationResult.enabled("System property matches");
330
} else {
331
return ConditionEvaluationResult.disabled(
332
String.format("System property [%s] does not match expected value [%s]",
333
systemProperty.name(), systemProperty.value()));
334
}
335
}
336
337
return ConditionEvaluationResult.enabled("No system property condition");
338
}
339
}
340
341
@Target({ElementType.TYPE, ElementType.METHOD})
342
@Retention(RetentionPolicy.RUNTIME)
343
@ExtendWith(SystemPropertyCondition.class)
344
@interface SystemProperty {
345
String name();
346
String value();
347
}
348
349
class ConditionalTest {
350
351
@Test
352
@SystemProperty(name = "env", value = "test")
353
void testOnlyInTestEnvironment() {
354
// Only runs when system property env=test
355
assertTrue(true);
356
}
357
}
358
```
359
360
### Test Instance Extensions
361
362
Control test instance creation and lifecycle.
363
364
```java { .api }
365
/**
366
* Create test instances
367
*/
368
interface TestInstanceFactory extends Extension {
369
/**
370
* Create test instance
371
*/
372
Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext)
373
throws TestInstantiationException;
374
}
375
376
/**
377
* Test instance factory context
378
*/
379
interface TestInstanceFactoryContext {
380
Class<?> getTestClass();
381
Optional<Object> getOuterInstance();
382
}
383
384
/**
385
* Callback before test instance construction
386
*/
387
interface TestInstancePreConstructCallback extends Extension {
388
void preConstructTestInstance(TestInstancePreConstructContext context, ExtensionContext extensionContext);
389
}
390
391
/**
392
* Process test instance after construction
393
*/
394
interface TestInstancePostProcessor extends Extension {
395
void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception;
396
}
397
398
/**
399
* Callback before test instance destruction
400
*/
401
interface TestInstancePreDestroyCallback extends Extension {
402
void preDestroyTestInstance(ExtensionContext context) throws Exception;
403
}
404
```
405
406
**Usage Example:**
407
408
```java
409
class DependencyInjectionExtension implements TestInstancePostProcessor {
410
411
@Override
412
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
413
Class<?> testClass = testInstance.getClass();
414
415
for (Field field : testClass.getDeclaredFields()) {
416
if (field.isAnnotationPresent(Inject.class)) {
417
field.setAccessible(true);
418
field.set(testInstance, createDependency(field.getType()));
419
}
420
}
421
}
422
423
private Object createDependency(Class<?> type) {
424
// Create dependency instance
425
if (type == UserService.class) {
426
return new UserService();
427
}
428
return null;
429
}
430
}
431
432
@Target(ElementType.FIELD)
433
@Retention(RetentionPolicy.RUNTIME)
434
@interface Inject {
435
}
436
437
@ExtendWith(DependencyInjectionExtension.class)
438
class ServiceTest {
439
440
@Inject
441
private UserService userService;
442
443
@Test
444
void testWithInjectedService() {
445
assertNotNull(userService);
446
// Use injected service
447
}
448
}
449
```
450
451
### Exception Handling Extensions
452
453
Handle test execution exceptions.
454
455
```java { .api }
456
/**
457
* Handle exceptions thrown during test execution
458
*/
459
interface TestExecutionExceptionHandler extends Extension {
460
/**
461
* Handle test execution exception
462
*/
463
void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
464
}
465
466
/**
467
* Callback before test interruption
468
*/
469
interface PreInterruptCallback extends Extension {
470
/**
471
* Called before test thread is interrupted
472
*/
473
void preInterrupt(PreInterruptContext context, ExtensionContext extensionContext) throws Exception;
474
}
475
476
/**
477
* Pre-interrupt context information
478
*/
479
interface PreInterruptContext {
480
Thread getThreadToInterrupt();
481
Optional<String> getReason();
482
}
483
484
/**
485
* Watch test execution and results
486
*/
487
interface TestWatcher extends Extension {
488
/**
489
* Called when test is disabled
490
*/
491
default void testDisabled(ExtensionContext context, Optional<String> reason) {}
492
493
/**
494
* Called when test succeeds
495
*/
496
default void testSuccessful(ExtensionContext context) {}
497
498
/**
499
* Called when test is aborted
500
*/
501
default void testAborted(ExtensionContext context, Throwable cause) {}
502
503
/**
504
* Called when test fails
505
*/
506
default void testFailed(ExtensionContext context, Throwable cause) {}
507
}
508
509
/**
510
* Intercept method invocations
511
*/
512
interface InvocationInterceptor extends Extension {
513
/**
514
* Intercept test method invocation
515
*/
516
default void interceptTestMethod(Invocation<Void> invocation,
517
ReflectiveInvocationContext<Method> invocationContext,
518
ExtensionContext extensionContext) throws Throwable {
519
invocation.proceed();
520
}
521
522
/**
523
* Intercept test class constructor invocation
524
*/
525
default <T> T interceptTestClassConstructor(Invocation<T> invocation,
526
ReflectiveInvocationContext<Constructor<T>> invocationContext,
527
ExtensionContext extensionContext) throws Throwable {
528
return invocation.proceed();
529
}
530
531
/**
532
* Intercept BeforeAll method invocation
533
*/
534
default void interceptBeforeAllMethod(Invocation<Void> invocation,
535
ReflectiveInvocationContext<Method> invocationContext,
536
ExtensionContext extensionContext) throws Throwable {
537
invocation.proceed();
538
}
539
540
/**
541
* Intercept BeforeEach method invocation
542
*/
543
default void interceptBeforeEachMethod(Invocation<Void> invocation,
544
ReflectiveInvocationContext<Method> invocationContext,
545
ExtensionContext extensionContext) throws Throwable {
546
invocation.proceed();
547
}
548
549
/**
550
* Intercept AfterEach method invocation
551
*/
552
default void interceptAfterEachMethod(Invocation<Void> invocation,
553
ReflectiveInvocationContext<Method> invocationContext,
554
ExtensionContext extensionContext) throws Throwable {
555
invocation.proceed();
556
}
557
558
/**
559
* Intercept AfterAll method invocation
560
*/
561
default void interceptAfterAllMethod(Invocation<Void> invocation,
562
ReflectiveInvocationContext<Method> invocationContext,
563
ExtensionContext extensionContext) throws Throwable {
564
invocation.proceed();
565
}
566
}
567
```
568
569
**Usage Example:**
570
571
```java
572
class RetryExtension implements TestExecutionExceptionHandler {
573
574
@Override
575
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
576
Optional<Retry> retryAnnotation = context.getElement()
577
.map(element -> element.getAnnotation(Retry.class));
578
579
if (retryAnnotation.isPresent()) {
580
int maxRetries = retryAnnotation.get().value();
581
ExtensionContext.Store store = getStore(context);
582
int retryCount = store.getOrComputeIfAbsent("retryCount", key -> 0, Integer.class);
583
584
if (retryCount < maxRetries) {
585
store.put("retryCount", retryCount + 1);
586
// Retry the test by not re-throwing the exception
587
return;
588
}
589
}
590
591
// Re-throw if no retry or max retries reached
592
throw throwable;
593
}
594
595
private ExtensionContext.Store getStore(ExtensionContext context) {
596
return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));
597
}
598
}
599
600
@Target(ElementType.METHOD)
601
@Retention(RetentionPolicy.RUNTIME)
602
@ExtendWith(RetryExtension.class)
603
@interface Retry {
604
int value() default 3;
605
}
606
607
class FlakeyTest {
608
609
@Test
610
@Retry(5)
611
void testThatMightFail() {
612
if (Math.random() < 0.7) {
613
throw new RuntimeException("Random failure");
614
}
615
assertTrue(true);
616
}
617
}
618
```
619
620
### Extension Context and Store
621
622
Access test context and store data across extension callbacks.
623
624
```java { .api }
625
/**
626
* Extension execution context
627
*/
628
interface ExtensionContext {
629
/**
630
* Get parent context
631
*/
632
Optional<ExtensionContext> getParent();
633
634
/**
635
* Get root context
636
*/
637
ExtensionContext getRoot();
638
639
/**
640
* Get unique ID
641
*/
642
String getUniqueId();
643
644
/**
645
* Get display name
646
*/
647
String getDisplayName();
648
649
/**
650
* Get all tags
651
*/
652
Set<String> getTags();
653
654
/**
655
* Get annotated element (class or method)
656
*/
657
Optional<AnnotatedElement> getElement();
658
659
/**
660
* Get test class
661
*/
662
Optional<Class<?>> getTestClass();
663
664
/**
665
* Get required test class (throws if not present)
666
*/
667
Class<?> getRequiredTestClass();
668
669
/**
670
* Get test instance lifecycle
671
*/
672
Optional<TestInstance.Lifecycle> getTestInstanceLifecycle();
673
674
/**
675
* Get test instance (may be null for static methods)
676
*/
677
Optional<Object> getTestInstance();
678
679
/**
680
* Get all test instances for nested tests
681
*/
682
Optional<TestInstances> getTestInstances();
683
684
/**
685
* Get test method
686
*/
687
Optional<Method> getTestMethod();
688
689
/**
690
* Get required test method (throws if not present)
691
*/
692
Method getRequiredTestMethod();
693
694
/**
695
* Get execution exception if test failed
696
*/
697
Optional<Throwable> getExecutionException();
698
699
/**
700
* Get configuration parameter
701
*/
702
Optional<String> getConfigurationParameter(String key);
703
704
/**
705
* Get store for sharing data
706
*/
707
Store getStore(Namespace namespace);
708
709
/**
710
* Publish entry to test report
711
*/
712
void publishReportEntry(Map<String, String> map);
713
void publishReportEntry(String key, String value);
714
715
/**
716
* Store namespace for organizing data
717
*/
718
class Namespace {
719
static Namespace create(Object... parts);
720
static final Namespace GLOBAL;
721
}
722
723
/**
724
* Key-value store for extension data
725
*/
726
interface Store {
727
Object get(Object key);
728
<V> V get(Object key, Class<V> requiredType);
729
<K, V> Object getOrComputeIfAbsent(K key, Function<K, V> defaultCreator);
730
<K, V> V getOrComputeIfAbsent(K key, Function<K, V> defaultCreator, Class<V> requiredType);
731
void put(Object key, Object value);
732
Object remove(Object key);
733
<V> V remove(Object key, Class<V> requiredType);
734
<K, V> V getOrDefault(K key, Class<V> requiredType, V defaultValue);
735
void clear();
736
737
interface CloseableResource {
738
void close() throws Throwable;
739
}
740
}
741
}
742
```
743
744
**Usage Example:**
745
746
```java
747
class DataSharingExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback {
748
749
@Override
750
public void beforeAll(ExtensionContext context) throws Exception {
751
// Store shared data for all tests
752
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.GLOBAL);
753
store.put("sharedData", new SharedTestData());
754
}
755
756
@Override
757
public void beforeEach(ExtensionContext context) throws Exception {
758
// Access test-specific information
759
String testName = context.getDisplayName();
760
Set<String> tags = context.getTags();
761
762
// Store per-test data
763
ExtensionContext.Store store = getStore(context);
764
store.put("testStartTime", System.currentTimeMillis());
765
766
System.out.printf("Starting test: %s with tags: %s%n", testName, tags);
767
}
768
769
@Override
770
public void afterAll(ExtensionContext context) throws Exception {
771
// Cleanup shared data
772
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.GLOBAL);
773
store.remove("sharedData");
774
}
775
776
private ExtensionContext.Store getStore(ExtensionContext context) {
777
return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));
778
}
779
}
780
```
781
782
## Additional Types
783
784
### Invocation and Context Types
785
786
Types used with InvocationInterceptor and other advanced extension features.
787
788
```java { .api }
789
/**
790
* Represents an invocation that can be proceeded
791
*/
792
interface Invocation<T> {
793
/**
794
* Proceed with the invocation
795
*/
796
T proceed() throws Throwable;
797
798
/**
799
* Skip the invocation
800
*/
801
void skip();
802
}
803
804
/**
805
* Context for reflective invocations
806
*/
807
interface ReflectiveInvocationContext<T> {
808
/**
809
* Get the executable being invoked (Constructor or Method)
810
*/
811
T getExecutable();
812
813
/**
814
* Get the arguments for the invocation
815
*/
816
List<Object> getArguments();
817
818
/**
819
* Get the target instance (null for static methods/constructors)
820
*/
821
Optional<Object> getTarget();
822
}
823
824
/**
825
* Test instances hierarchy for nested tests
826
*/
827
interface TestInstances {
828
/**
829
* Get the innermost (most nested) test instance
830
*/
831
Object getInnermostInstance();
832
833
/**
834
* Get all test instances from outermost to innermost
835
*/
836
List<Object> getEnclosingInstances();
837
838
/**
839
* Get all test instances (enclosing + innermost)
840
*/
841
List<Object> getAllInstances();
842
843
/**
844
* Find test instance of specific type
845
*/
846
<T> Optional<T> findInstance(Class<T> requiredType);
847
}
848
```