Spring TestContext Framework for comprehensive integration testing of Spring applications
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.
The core JUnit Jupiter extension that integrates Spring TestContext Framework with JUnit 5.
/**
* SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter programming model.
*
* To use this extension, simply annotate a JUnit Jupiter based test class with @ExtendWith(SpringExtension.class)
* or use composite annotations like @SpringJUnitConfig.
*/
public class SpringExtension implements BeforeAllCallback, AfterAllCallback,
TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback,
BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver {
/**
* Delegates to TestContextManager.beforeTestClass().
* @param context the current extension context
*/
@Override
public void beforeAll(ExtensionContext context) throws Exception;
/**
* Delegates to TestContextManager.afterTestClass().
* @param context the current extension context
*/
@Override
public void afterAll(ExtensionContext context) throws Exception;
/**
* Delegates to TestContextManager.prepareTestInstance().
* @param testInstance the test instance to post-process
* @param context the current extension context
*/
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception;
/**
* Delegates to TestContextManager.beforeTestMethod().
* @param context the current extension context
*/
@Override
public void beforeEach(ExtensionContext context) throws Exception;
/**
* Delegates to TestContextManager.beforeTestExecution().
* @param context the current extension context
*/
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception;
/**
* Delegates to TestContextManager.afterTestExecution().
* @param context the current extension context
*/
@Override
public void afterTestExecution(ExtensionContext context) throws Exception;
/**
* Delegates to TestContextManager.afterTestMethod().
* @param context the current extension context
*/
@Override
public void afterEach(ExtensionContext context) throws Exception;
/**
* Resolve parameters from the Spring ApplicationContext if the parameter is of a supported type.
* @param parameterContext the context for the parameter for which an argument should be resolved
* @param extensionContext the extension context for the Executable about to be invoked
* @return true if this resolver can resolve an argument for the parameter
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
/**
* Resolve a parameter from the Spring ApplicationContext.
* @param parameterContext the context for the parameter for which an argument should be resolved
* @param extensionContext the extension context for the Executable about to be invoked
* @return the resolved argument for the parameter
* @throws ParameterResolutionException if the parameter cannot be resolved
*/
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
}Convenient composed annotations that combine Spring testing features with JUnit Jupiter.
/**
* @SpringJUnitConfig is a composed annotation that combines @ExtendWith(SpringExtension.class)
* from JUnit Jupiter with @ContextConfiguration from the Spring TestContext Framework.
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SpringJUnitConfig {
/**
* Alias for classes().
* @return an array of configuration classes
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
Class<?>[] value() default {};
/**
* The component classes to use for loading an ApplicationContext.
* @return an array of configuration classes
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
Class<?>[] classes() default {};
/**
* The resource locations to use for loading an ApplicationContext.
* @return an array of resource locations
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] locations() default {};
/**
* The application context initializer classes to use for initializing a ConfigurableApplicationContext.
* @return an array of initializer classes
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "initializers")
Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
/**
* Whether or not resource locations or component classes from test superclasses should be inherited.
* @return true if locations should be inherited
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "inheritLocations")
boolean inheritLocations() default true;
/**
* Whether or not context initializers from test superclasses should be inherited.
* @return true if initializers should be inherited
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "inheritInitializers")
boolean inheritInitializers() default true;
/**
* The name of the context hierarchy level represented by this configuration.
* @return the context hierarchy level name
*/
@AliasFor(annotation = ContextConfiguration.class, attribute = "name")
String name() default "";
}
/**
* @SpringJUnitWebConfig is a composed annotation that combines @SpringJUnitConfig
* with @WebAppConfiguration from the Spring TestContext Framework.
*/
@SpringJUnitConfig
@WebAppConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SpringJUnitWebConfig {
/**
* Alias for classes().
* @return an array of configuration classes
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "classes")
Class<?>[] value() default {};
/**
* The component classes to use for loading an ApplicationContext.
* @return an array of configuration classes
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "classes")
Class<?>[] classes() default {};
/**
* The resource locations to use for loading an ApplicationContext.
* @return an array of resource locations
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "locations")
String[] locations() default {};
/**
* The application context initializer classes to use for initializing a ConfigurableApplicationContext.
* @return an array of initializer classes
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "initializers")
Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
/**
* Whether or not resource locations or component classes from test superclasses should be inherited.
* @return true if locations should be inherited
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "inheritLocations")
boolean inheritLocations() default true;
/**
* Whether or not context initializers from test superclasses should be inherited.
* @return true if initializers should be inherited
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "inheritInitializers")
boolean inheritInitializers() default true;
/**
* The name of the context hierarchy level represented by this configuration.
* @return the context hierarchy level name
*/
@AliasFor(annotation = SpringJUnitConfig.class, attribute = "name")
String name() default "";
/**
* The resource path to the root of the web application.
* @return the resource path for the web application
*/
@AliasFor(annotation = WebAppConfiguration.class, attribute = "value")
String resourcePath() default "src/main/webapp";
}JUnit Jupiter extensions for conditional test execution based on Spring profiles and SpEL expressions.
/**
* @EnabledIf is used to signal that the annotated test class or test method is enabled
* and should be executed if the supplied SpEL expression evaluates to true.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(SpringJUnitCondition.class)
public @interface EnabledIf {
/**
* The SpEL expression to evaluate.
* @return the SpEL expression
*/
String expression();
/**
* The reason this test is enabled.
* @return the reason
*/
String reason() default "";
/**
* Whether the ApplicationContext should be eagerly loaded to evaluate the expression.
* @return true if the context should be loaded
*/
boolean loadContext() default false;
}
/**
* @DisabledIf is used to signal that the annotated test class or test method is disabled
* and should not be executed if the supplied SpEL expression evaluates to true.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(SpringJUnitCondition.class)
public @interface DisabledIf {
/**
* The SpEL expression to evaluate.
* @return the SpEL expression
*/
String expression();
/**
* The reason this test is disabled.
* @return the reason
*/
String reason() default "";
/**
* Whether the ApplicationContext should be eagerly loaded to evaluate the expression.
* @return true if the context should be loaded
*/
boolean loadContext() default false;
}
/**
* SpringJUnitCondition is a JUnit Jupiter ExecutionCondition that supports the @EnabledIf and @DisabledIf annotations.
*/
public class SpringJUnitCondition implements ExecutionCondition {
/**
* Evaluate the condition for the given extension context.
* @param context the current extension context
* @return the evaluation result
*/
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);
}Support for dependency injection in test method parameters.
/**
* ApplicationContextParameterResolver resolves method parameters of type ApplicationContext
* (or sub-types thereof) for JUnit Jupiter test methods.
*/
public class ApplicationContextParameterResolver implements ParameterResolver {
/**
* Determine if this resolver supports the given parameter.
* @param parameterContext the parameter context
* @param extensionContext the extension context
* @return true if the parameter is supported
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
/**
* Resolve the parameter from the ApplicationContext.
* @param parameterContext the parameter context
* @param extensionContext the extension context
* @return the resolved parameter value
*/
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
}Usage Examples:
import org.springframework.test.context.junit.jupiter.*;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
// Basic Spring JUnit integration
@SpringJUnitConfig(AppConfig.class)
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Test
void shouldInjectDependencies() {
assertThat(userService).isNotNull();
User user = new User("John", "john@example.com");
User saved = userService.save(user);
assertThat(saved.getId()).isNotNull();
}
@Test
void shouldSupportParameterInjection(UserRepository userRepository) {
// Parameters can be injected from ApplicationContext
assertThat(userRepository).isNotNull();
long count = userRepository.count();
assertThat(count).isGreaterThanOrEqualTo(0);
}
}
// Web application testing
@SpringJUnitWebConfig(WebConfig.class)
class WebControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Test
void shouldLoadWebApplicationContext() {
assertThat(webApplicationContext).isNotNull();
assertThat(webApplicationContext.getServletContext()).isNotNull();
}
@Test
void shouldPerformWebRequests() throws Exception {
mockMvc.perform(get("/users"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
}
// Multiple configuration sources
@SpringJUnitConfig(locations = {"classpath:app-config.xml", "classpath:test-config.xml"})
class XmlConfigIntegrationTest {
@Autowired
private DataSource dataSource;
@Test
void shouldLoadXmlConfiguration() {
assertThat(dataSource).isNotNull();
}
}
// Conditional test execution
@SpringJUnitConfig(ConditionalTestConfig.class)
class ConditionalExecutionTest {
@Test
@EnabledIf("#{environment.acceptsProfiles('integration')}")
void shouldRunOnlyInIntegrationProfile() {
// This test runs only when 'integration' profile is active
}
@Test
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('linux')}")
void shouldRunOnlyOnLinux() {
// This test runs only on Linux systems
}
@Test
@DisabledIf("#{T(java.time.LocalTime).now().hour < 9}")
void shouldNotRunBeforeNineAM() {
// This test is disabled before 9 AM
}
@Test
@EnabledIf(expression = "#{@testDataService.hasTestData()}", loadContext = true)
void shouldRunWhenTestDataExists() {
// This test runs only when test data exists (requires context loading)
}
}
// Nested test classes with Spring
@SpringJUnitConfig(NestedTestConfig.class)
class NestedSpringTests {
@Autowired
private UserService userService;
@Test
void shouldHaveUserService() {
assertThat(userService).isNotNull();
}
@Nested
@TestPropertySource(properties = "feature.enabled=true")
class WhenFeatureEnabled {
@Autowired
private FeatureService featureService;
@Test
void shouldEnableFeature() {
assertThat(featureService.isEnabled()).isTrue();
}
@Test
@EnabledIf("#{@featureService.isEnabled()}")
void shouldRunWhenFeatureIsEnabled() {
// Test feature-specific behavior
}
}
@Nested
@ActiveProfiles("mock")
class WithMockProfile {
@Test
void shouldUseMockBeans() {
// Tests with mock profile active
}
}
}
// Parameterized tests with Spring
@SpringJUnitConfig(ValidationTestConfig.class)
class ParameterizedSpringTest {
@Autowired
private ValidationService validationService;
@ParameterizedTest
@ValueSource(strings = {"john@example.com", "jane@test.org", "bob@company.net"})
void shouldValidateEmails(String email) {
ValidationResult result = validationService.validateEmail(email);
assertThat(result.isValid()).isTrue();
}
@ParameterizedTest
@CsvSource({
"John, john@example.com, true",
"'', invalid-email, false",
"Jane, jane@test.org, true"
})
void shouldValidateUsers(String name, String email, boolean expectedValid) {
User user = new User(name, email);
ValidationResult result = validationService.validateUser(user);
assertThat(result.isValid()).isEqualTo(expectedValid);
}
}
// Dynamic tests with Spring context
@SpringJUnitConfig(DynamicTestConfig.class)
class DynamicSpringTests {
@Autowired
private TestDataService testDataService;
@TestFactory
Stream<DynamicTest> shouldTestAllUsers() {
return testDataService.getAllTestUsers().stream()
.map(user -> DynamicTest.dynamicTest(
"Testing user: " + user.getName(),
() -> {
assertThat(user.getId()).isNotNull();
assertThat(user.getName()).isNotBlank();
assertThat(user.getEmail()).contains("@");
}
));
}
@TestFactory
Collection<DynamicTest> shouldValidateAllConfigurations(ConfigurationService configService) {
return configService.getAllConfigurations().stream()
.map(config -> DynamicTest.dynamicTest(
"Validating config: " + config.getName(),
() -> assertThat(configService.isValid(config)).isTrue()
))
.toList();
}
}
// Test lifecycle callbacks
@SpringJUnitConfig(LifecycleTestConfig.class)
class TestLifecycleExample {
@Autowired
private DatabaseService databaseService;
@BeforeAll
static void setupClass() {
// Class-level setup
System.setProperty("test.environment", "junit5");
}
@BeforeEach
void setUp() {
// Method-level setup - Spring context is available here
databaseService.clearCache();
}
@Test
void shouldHaveCleanState() {
assertThat(databaseService.getCacheSize()).isZero();
}
@AfterEach
void tearDown() {
// Method-level cleanup
databaseService.resetToDefaults();
}
@AfterAll
static void tearDownClass() {
// Class-level cleanup
System.clearProperty("test.environment");
}
}/**
* Encapsulates the context in which a test is executed, agnostic of the actual testing framework in use.
* Used by SpringExtension to bridge JUnit Jupiter and Spring TestContext Framework.
*/
public interface TestContext {
/**
* Get the application context for this test context.
* @return the application context (never null)
* @throws IllegalStateException if an error occurs while retrieving the application context
*/
ApplicationContext getApplicationContext();
/**
* Get the test class for this test context.
* @return the test class (never null)
*/
Class<?> getTestClass();
/**
* Get the current test instance for this test context.
* @return the current test instance (may be null)
*/
@Nullable
Object getTestInstance();
/**
* Get the current test method for this test context.
* @return the current test method (may be null)
*/
@Nullable
Method getTestMethod();
}
/**
* Strategy interface for resolving test method parameters from a Spring ApplicationContext.
*/
public interface TestContextParameterResolver {
/**
* Determine if this resolver can resolve the given parameter.
* @param parameter the parameter to resolve
* @param testClass the test class
* @param applicationContext the Spring application context
* @return true if the parameter can be resolved
*/
boolean supportsParameter(Parameter parameter, Class<?> testClass, ApplicationContext applicationContext);
/**
* Resolve the given parameter from the application context.
* @param parameter the parameter to resolve
* @param testClass the test class
* @param applicationContext the Spring application context
* @return the resolved parameter value
*/
Object resolveParameter(Parameter parameter, Class<?> testClass, ApplicationContext applicationContext);
}
/**
* Exception thrown when parameter resolution fails in Spring test context.
*/
public class ParameterResolutionException extends RuntimeException {
/**
* Create a new ParameterResolutionException.
* @param message the detail message
*/
public ParameterResolutionException(String message);
/**
* Create a new ParameterResolutionException.
* @param message the detail message
* @param cause the root cause
*/
public ParameterResolutionException(String message, @Nullable Throwable cause);
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring-test