0
# JUnit Integration
1
2
Spring Test provides seamless integration with JUnit Jupiter (JUnit 5) through the SpringExtension and composite annotations. This integration enables Spring-specific features like dependency injection, transaction management, and context configuration in JUnit tests.
3
4
## Capabilities
5
6
### Spring Extension
7
8
The core JUnit Jupiter extension that integrates Spring TestContext Framework with JUnit 5.
9
10
```java { .api }
11
/**
12
* SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter programming model.
13
*
14
* To use this extension, simply annotate a JUnit Jupiter based test class with @ExtendWith(SpringExtension.class)
15
* or use composite annotations like @SpringJUnitConfig.
16
*/
17
public class SpringExtension implements BeforeAllCallback, AfterAllCallback,
18
TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback,
19
BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver {
20
21
/**
22
* Delegates to TestContextManager.beforeTestClass().
23
* @param context the current extension context
24
*/
25
@Override
26
public void beforeAll(ExtensionContext context) throws Exception;
27
28
/**
29
* Delegates to TestContextManager.afterTestClass().
30
* @param context the current extension context
31
*/
32
@Override
33
public void afterAll(ExtensionContext context) throws Exception;
34
35
/**
36
* Delegates to TestContextManager.prepareTestInstance().
37
* @param testInstance the test instance to post-process
38
* @param context the current extension context
39
*/
40
@Override
41
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception;
42
43
/**
44
* Delegates to TestContextManager.beforeTestMethod().
45
* @param context the current extension context
46
*/
47
@Override
48
public void beforeEach(ExtensionContext context) throws Exception;
49
50
/**
51
* Delegates to TestContextManager.beforeTestExecution().
52
* @param context the current extension context
53
*/
54
@Override
55
public void beforeTestExecution(ExtensionContext context) throws Exception;
56
57
/**
58
* Delegates to TestContextManager.afterTestExecution().
59
* @param context the current extension context
60
*/
61
@Override
62
public void afterTestExecution(ExtensionContext context) throws Exception;
63
64
/**
65
* Delegates to TestContextManager.afterTestMethod().
66
* @param context the current extension context
67
*/
68
@Override
69
public void afterEach(ExtensionContext context) throws Exception;
70
71
/**
72
* Resolve parameters from the Spring ApplicationContext if the parameter is of a supported type.
73
* @param parameterContext the context for the parameter for which an argument should be resolved
74
* @param extensionContext the extension context for the Executable about to be invoked
75
* @return true if this resolver can resolve an argument for the parameter
76
*/
77
@Override
78
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
79
80
/**
81
* Resolve a parameter from the Spring ApplicationContext.
82
* @param parameterContext the context for the parameter for which an argument should be resolved
83
* @param extensionContext the extension context for the Executable about to be invoked
84
* @return the resolved argument for the parameter
85
* @throws ParameterResolutionException if the parameter cannot be resolved
86
*/
87
@Override
88
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
89
}
90
```
91
92
### Composite Annotations
93
94
Convenient composed annotations that combine Spring testing features with JUnit Jupiter.
95
96
```java { .api }
97
/**
98
* @SpringJUnitConfig is a composed annotation that combines @ExtendWith(SpringExtension.class)
99
* from JUnit Jupiter with @ContextConfiguration from the Spring TestContext Framework.
100
*/
101
@ExtendWith(SpringExtension.class)
102
@ContextConfiguration
103
@Target(ElementType.TYPE)
104
@Retention(RetentionPolicy.RUNTIME)
105
@Documented
106
@Inherited
107
public @interface SpringJUnitConfig {
108
109
/**
110
* Alias for classes().
111
* @return an array of configuration classes
112
*/
113
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
114
Class<?>[] value() default {};
115
116
/**
117
* The component classes to use for loading an ApplicationContext.
118
* @return an array of configuration classes
119
*/
120
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
121
Class<?>[] classes() default {};
122
123
/**
124
* The resource locations to use for loading an ApplicationContext.
125
* @return an array of resource locations
126
*/
127
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
128
String[] locations() default {};
129
130
/**
131
* The application context initializer classes to use for initializing a ConfigurableApplicationContext.
132
* @return an array of initializer classes
133
*/
134
@AliasFor(annotation = ContextConfiguration.class, attribute = "initializers")
135
Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
136
137
/**
138
* Whether or not resource locations or component classes from test superclasses should be inherited.
139
* @return true if locations should be inherited
140
*/
141
@AliasFor(annotation = ContextConfiguration.class, attribute = "inheritLocations")
142
boolean inheritLocations() default true;
143
144
/**
145
* Whether or not context initializers from test superclasses should be inherited.
146
* @return true if initializers should be inherited
147
*/
148
@AliasFor(annotation = ContextConfiguration.class, attribute = "inheritInitializers")
149
boolean inheritInitializers() default true;
150
151
/**
152
* The name of the context hierarchy level represented by this configuration.
153
* @return the context hierarchy level name
154
*/
155
@AliasFor(annotation = ContextConfiguration.class, attribute = "name")
156
String name() default "";
157
}
158
159
/**
160
* @SpringJUnitWebConfig is a composed annotation that combines @SpringJUnitConfig
161
* with @WebAppConfiguration from the Spring TestContext Framework.
162
*/
163
@SpringJUnitConfig
164
@WebAppConfiguration
165
@Target(ElementType.TYPE)
166
@Retention(RetentionPolicy.RUNTIME)
167
@Documented
168
@Inherited
169
public @interface SpringJUnitWebConfig {
170
171
/**
172
* Alias for classes().
173
* @return an array of configuration classes
174
*/
175
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "classes")
176
Class<?>[] value() default {};
177
178
/**
179
* The component classes to use for loading an ApplicationContext.
180
* @return an array of configuration classes
181
*/
182
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "classes")
183
Class<?>[] classes() default {};
184
185
/**
186
* The resource locations to use for loading an ApplicationContext.
187
* @return an array of resource locations
188
*/
189
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "locations")
190
String[] locations() default {};
191
192
/**
193
* The application context initializer classes to use for initializing a ConfigurableApplicationContext.
194
* @return an array of initializer classes
195
*/
196
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "initializers")
197
Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
198
199
/**
200
* Whether or not resource locations or component classes from test superclasses should be inherited.
201
* @return true if locations should be inherited
202
*/
203
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "inheritLocations")
204
boolean inheritLocations() default true;
205
206
/**
207
* Whether or not context initializers from test superclasses should be inherited.
208
* @return true if initializers should be inherited
209
*/
210
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "inheritInitializers")
211
boolean inheritInitializers() default true;
212
213
/**
214
* The name of the context hierarchy level represented by this configuration.
215
* @return the context hierarchy level name
216
*/
217
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "name")
218
String name() default "";
219
220
/**
221
* The resource path to the root of the web application.
222
* @return the resource path for the web application
223
*/
224
@AliasFor(annotation = WebAppConfiguration.class, attribute = "value")
225
String resourcePath() default "src/main/webapp";
226
}
227
```
228
229
### Conditional Test Execution
230
231
JUnit Jupiter extensions for conditional test execution based on Spring profiles and SpEL expressions.
232
233
```java { .api }
234
/**
235
* @EnabledIf is used to signal that the annotated test class or test method is enabled
236
* and should be executed if the supplied SpEL expression evaluates to true.
237
*/
238
@Target({ElementType.TYPE, ElementType.METHOD})
239
@Retention(RetentionPolicy.RUNTIME)
240
@Documented
241
@ExtendWith(SpringJUnitCondition.class)
242
public @interface EnabledIf {
243
244
/**
245
* The SpEL expression to evaluate.
246
* @return the SpEL expression
247
*/
248
String expression();
249
250
/**
251
* The reason this test is enabled.
252
* @return the reason
253
*/
254
String reason() default "";
255
256
/**
257
* Whether the ApplicationContext should be eagerly loaded to evaluate the expression.
258
* @return true if the context should be loaded
259
*/
260
boolean loadContext() default false;
261
}
262
263
/**
264
* @DisabledIf is used to signal that the annotated test class or test method is disabled
265
* and should not be executed if the supplied SpEL expression evaluates to true.
266
*/
267
@Target({ElementType.TYPE, ElementType.METHOD})
268
@Retention(RetentionPolicy.RUNTIME)
269
@Documented
270
@ExtendWith(SpringJUnitCondition.class)
271
public @interface DisabledIf {
272
273
/**
274
* The SpEL expression to evaluate.
275
* @return the SpEL expression
276
*/
277
String expression();
278
279
/**
280
* The reason this test is disabled.
281
* @return the reason
282
*/
283
String reason() default "";
284
285
/**
286
* Whether the ApplicationContext should be eagerly loaded to evaluate the expression.
287
* @return true if the context should be loaded
288
*/
289
boolean loadContext() default false;
290
}
291
292
/**
293
* SpringJUnitCondition is a JUnit Jupiter ExecutionCondition that supports the @EnabledIf and @DisabledIf annotations.
294
*/
295
public class SpringJUnitCondition implements ExecutionCondition {
296
297
/**
298
* Evaluate the condition for the given extension context.
299
* @param context the current extension context
300
* @return the evaluation result
301
*/
302
@Override
303
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);
304
}
305
```
306
307
### Parameter Resolution
308
309
Support for dependency injection in test method parameters.
310
311
```java { .api }
312
/**
313
* ApplicationContextParameterResolver resolves method parameters of type ApplicationContext
314
* (or sub-types thereof) for JUnit Jupiter test methods.
315
*/
316
public class ApplicationContextParameterResolver implements ParameterResolver {
317
318
/**
319
* Determine if this resolver supports the given parameter.
320
* @param parameterContext the parameter context
321
* @param extensionContext the extension context
322
* @return true if the parameter is supported
323
*/
324
@Override
325
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
326
327
/**
328
* Resolve the parameter from the ApplicationContext.
329
* @param parameterContext the parameter context
330
* @param extensionContext the extension context
331
* @return the resolved parameter value
332
*/
333
@Override
334
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
335
}
336
```
337
338
**Usage Examples:**
339
340
```java
341
import org.springframework.test.context.junit.jupiter.*;
342
import org.junit.jupiter.api.*;
343
import org.springframework.beans.factory.annotation.Autowired;
344
345
// Basic Spring JUnit integration
346
@SpringJUnitConfig(AppConfig.class)
347
class UserServiceIntegrationTest {
348
349
@Autowired
350
private UserService userService;
351
352
@Test
353
void shouldInjectDependencies() {
354
assertThat(userService).isNotNull();
355
356
User user = new User("John", "john@example.com");
357
User saved = userService.save(user);
358
assertThat(saved.getId()).isNotNull();
359
}
360
361
@Test
362
void shouldSupportParameterInjection(UserRepository userRepository) {
363
// Parameters can be injected from ApplicationContext
364
assertThat(userRepository).isNotNull();
365
366
long count = userRepository.count();
367
assertThat(count).isGreaterThanOrEqualTo(0);
368
}
369
}
370
371
// Web application testing
372
@SpringJUnitWebConfig(WebConfig.class)
373
class WebControllerIntegrationTest {
374
375
@Autowired
376
private MockMvc mockMvc;
377
378
@Autowired
379
private WebApplicationContext webApplicationContext;
380
381
@Test
382
void shouldLoadWebApplicationContext() {
383
assertThat(webApplicationContext).isNotNull();
384
assertThat(webApplicationContext.getServletContext()).isNotNull();
385
}
386
387
@Test
388
void shouldPerformWebRequests() throws Exception {
389
mockMvc.perform(get("/users"))
390
.andExpect(status().isOk())
391
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
392
}
393
}
394
395
// Multiple configuration sources
396
@SpringJUnitConfig(locations = {"classpath:app-config.xml", "classpath:test-config.xml"})
397
class XmlConfigIntegrationTest {
398
399
@Autowired
400
private DataSource dataSource;
401
402
@Test
403
void shouldLoadXmlConfiguration() {
404
assertThat(dataSource).isNotNull();
405
}
406
}
407
408
// Conditional test execution
409
@SpringJUnitConfig(ConditionalTestConfig.class)
410
class ConditionalExecutionTest {
411
412
@Test
413
@EnabledIf("#{environment.acceptsProfiles('integration')}")
414
void shouldRunOnlyInIntegrationProfile() {
415
// This test runs only when 'integration' profile is active
416
}
417
418
@Test
419
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('linux')}")
420
void shouldRunOnlyOnLinux() {
421
// This test runs only on Linux systems
422
}
423
424
@Test
425
@DisabledIf("#{T(java.time.LocalTime).now().hour < 9}")
426
void shouldNotRunBeforeNineAM() {
427
// This test is disabled before 9 AM
428
}
429
430
@Test
431
@EnabledIf(expression = "#{@testDataService.hasTestData()}", loadContext = true)
432
void shouldRunWhenTestDataExists() {
433
// This test runs only when test data exists (requires context loading)
434
}
435
}
436
437
// Nested test classes with Spring
438
@SpringJUnitConfig(NestedTestConfig.class)
439
class NestedSpringTests {
440
441
@Autowired
442
private UserService userService;
443
444
@Test
445
void shouldHaveUserService() {
446
assertThat(userService).isNotNull();
447
}
448
449
@Nested
450
@TestPropertySource(properties = "feature.enabled=true")
451
class WhenFeatureEnabled {
452
453
@Autowired
454
private FeatureService featureService;
455
456
@Test
457
void shouldEnableFeature() {
458
assertThat(featureService.isEnabled()).isTrue();
459
}
460
461
@Test
462
@EnabledIf("#{@featureService.isEnabled()}")
463
void shouldRunWhenFeatureIsEnabled() {
464
// Test feature-specific behavior
465
}
466
}
467
468
@Nested
469
@ActiveProfiles("mock")
470
class WithMockProfile {
471
472
@Test
473
void shouldUseMockBeans() {
474
// Tests with mock profile active
475
}
476
}
477
}
478
479
// Parameterized tests with Spring
480
@SpringJUnitConfig(ValidationTestConfig.class)
481
class ParameterizedSpringTest {
482
483
@Autowired
484
private ValidationService validationService;
485
486
@ParameterizedTest
487
@ValueSource(strings = {"john@example.com", "jane@test.org", "bob@company.net"})
488
void shouldValidateEmails(String email) {
489
ValidationResult result = validationService.validateEmail(email);
490
assertThat(result.isValid()).isTrue();
491
}
492
493
@ParameterizedTest
494
@CsvSource({
495
"John, john@example.com, true",
496
"'', invalid-email, false",
497
"Jane, jane@test.org, true"
498
})
499
void shouldValidateUsers(String name, String email, boolean expectedValid) {
500
User user = new User(name, email);
501
ValidationResult result = validationService.validateUser(user);
502
assertThat(result.isValid()).isEqualTo(expectedValid);
503
}
504
}
505
506
// Dynamic tests with Spring context
507
@SpringJUnitConfig(DynamicTestConfig.class)
508
class DynamicSpringTests {
509
510
@Autowired
511
private TestDataService testDataService;
512
513
@TestFactory
514
Stream<DynamicTest> shouldTestAllUsers() {
515
return testDataService.getAllTestUsers().stream()
516
.map(user -> DynamicTest.dynamicTest(
517
"Testing user: " + user.getName(),
518
() -> {
519
assertThat(user.getId()).isNotNull();
520
assertThat(user.getName()).isNotBlank();
521
assertThat(user.getEmail()).contains("@");
522
}
523
));
524
}
525
526
@TestFactory
527
Collection<DynamicTest> shouldValidateAllConfigurations(ConfigurationService configService) {
528
return configService.getAllConfigurations().stream()
529
.map(config -> DynamicTest.dynamicTest(
530
"Validating config: " + config.getName(),
531
() -> assertThat(configService.isValid(config)).isTrue()
532
))
533
.toList();
534
}
535
}
536
537
// Test lifecycle callbacks
538
@SpringJUnitConfig(LifecycleTestConfig.class)
539
class TestLifecycleExample {
540
541
@Autowired
542
private DatabaseService databaseService;
543
544
@BeforeAll
545
static void setupClass() {
546
// Class-level setup
547
System.setProperty("test.environment", "junit5");
548
}
549
550
@BeforeEach
551
void setUp() {
552
// Method-level setup - Spring context is available here
553
databaseService.clearCache();
554
}
555
556
@Test
557
void shouldHaveCleanState() {
558
assertThat(databaseService.getCacheSize()).isZero();
559
}
560
561
@AfterEach
562
void tearDown() {
563
// Method-level cleanup
564
databaseService.resetToDefaults();
565
}
566
567
@AfterAll
568
static void tearDownClass() {
569
// Class-level cleanup
570
System.clearProperty("test.environment");
571
}
572
}
573
```
574
575
## Types
576
577
```java { .api }
578
/**
579
* Encapsulates the context in which a test is executed, agnostic of the actual testing framework in use.
580
* Used by SpringExtension to bridge JUnit Jupiter and Spring TestContext Framework.
581
*/
582
public interface TestContext {
583
584
/**
585
* Get the application context for this test context.
586
* @return the application context (never null)
587
* @throws IllegalStateException if an error occurs while retrieving the application context
588
*/
589
ApplicationContext getApplicationContext();
590
591
/**
592
* Get the test class for this test context.
593
* @return the test class (never null)
594
*/
595
Class<?> getTestClass();
596
597
/**
598
* Get the current test instance for this test context.
599
* @return the current test instance (may be null)
600
*/
601
@Nullable
602
Object getTestInstance();
603
604
/**
605
* Get the current test method for this test context.
606
* @return the current test method (may be null)
607
*/
608
@Nullable
609
Method getTestMethod();
610
}
611
612
/**
613
* Strategy interface for resolving test method parameters from a Spring ApplicationContext.
614
*/
615
public interface TestContextParameterResolver {
616
617
/**
618
* Determine if this resolver can resolve the given parameter.
619
* @param parameter the parameter to resolve
620
* @param testClass the test class
621
* @param applicationContext the Spring application context
622
* @return true if the parameter can be resolved
623
*/
624
boolean supportsParameter(Parameter parameter, Class<?> testClass, ApplicationContext applicationContext);
625
626
/**
627
* Resolve the given parameter from the application context.
628
* @param parameter the parameter to resolve
629
* @param testClass the test class
630
* @param applicationContext the Spring application context
631
* @return the resolved parameter value
632
*/
633
Object resolveParameter(Parameter parameter, Class<?> testClass, ApplicationContext applicationContext);
634
}
635
636
/**
637
* Exception thrown when parameter resolution fails in Spring test context.
638
*/
639
public class ParameterResolutionException extends RuntimeException {
640
641
/**
642
* Create a new ParameterResolutionException.
643
* @param message the detail message
644
*/
645
public ParameterResolutionException(String message);
646
647
/**
648
* Create a new ParameterResolutionException.
649
* @param message the detail message
650
* @param cause the root cause
651
*/
652
public ParameterResolutionException(String message, @Nullable Throwable cause);
653
}
654
```