In Spring Core testing, the ContextCustomizer interface allows test infrastructure to modify the ApplicationContext after bean definitions are loaded but before the context is refreshed.
This mechanism is primarily used to support advanced testing features such as dynamic bean registration, environment mutation, or conditional infrastructure setup without polluting user configuration classes.
Definition of ContextCustomizerVersion: 7.0.3 package org.springframework.test.context;
@FunctionalInterface
public interface ContextCustomizer {
void customizeContext(ConfigurableApplicationContext context, 1
MergedContextConfiguration mergedConfig);
}
Why ContextCustomizer is needed
Traditional configuration mechanisms like @Configuration and @TestPropertySource operate at a declarative level. ContextCustomizer exists to support programmatic, test-driven customization of the test context lifecycle.
ContextCustomizerFactory
ContextCustomizerFactory is the factory for creating ContextCustomizers. All we need to do is to implement this interface.
Definition of ContextCustomizerFactoryVersion: 7.0.3 package org.springframework.test.context;
@FunctionalInterface
public interface ContextCustomizerFactory {
ContextCustomizer createContextCustomizer(Class<?> testClass, 1
List<ContextConfigurationAttributes>
configAttributes);
}
Registering the ContextCustomizerFactory implementation
The ContextCustomizerFactory can be registered with the Spring test framework in two primary ways:
Via the spring.factories file: Create or edit the file src/test/resources/META-INF/spring.factories and declare the factory class: org.springframework.test.context.ContextCustomizerFactory=com.example.MyCustomizerFactory Use this method if the factory to be registered globality for all tests.
Via the @ContextCustomizerFactories annotation: Annotate your test class with @ContextCustomizerFactories and specify the factory class: @ContextCustomizerFactories(MyCustomizerFactory.class)
Definition of ContextCustomizerFactoriesVersion: 7.0.3 package org.springframework.test.context;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ContextCustomizerFactories {
@AliasFor("factories")
Class<? extends ContextCustomizerFactory>[] value() default {}; 1
@AliasFor("value")
Class<? extends ContextCustomizerFactory>[] factories() default {}; 2
boolean inheritFactories() default true; 3
MergeMode mergeMode() default MergeMode.MERGE_WITH_DEFAULTS; 4
enum MergeMode { 5
MERGE_WITH_DEFAULTS, 6
REPLACE_DEFAULTS 7
}
}
Example
package com.logicbig.example;
public class TestService {
public String value() {
return "customized";
}
}
Implementing ContextCustomizer
package com.logicbig.example;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
public class TestServiceContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(
ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
GenericApplicationContext gac =
(GenericApplicationContext) context;
gac.registerBean(TestService.class, TestService::new);
}
}
Implementing ContextCustomizerFactory
package com.logicbig.example;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import java.util.List;
public class TestServiceContextCustomizerFactory
implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(
Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return new TestServiceContextCustomizer();
}
}
Test Class
package com.logicbig.example;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringJUnitConfig
@ContextCustomizerFactories(
TestServiceContextCustomizerFactory.class)
class ContextCustomizerFactoriesTest {
@Autowired
private TestService testService;
@Test
void verifyContextCustomizerInjection() {
assertEquals("customized", testService.value());
}
@Configuration
static class TestConfig{}
}
Output$ mvn test [INFO] Scanning for projects... [INFO] [INFO] --------------< com.logicbig.example:context-customizers >-------------- [INFO] Building context-customizers 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ context-customizers --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-core-testing\context-customizers\src\main\resources [INFO] [INFO] --- compiler:3.11.0:compile (default-compile) @ context-customizers --- [INFO] No sources to compile [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ context-customizers --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-core-testing\context-customizers\src\test\resources [INFO] [INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ context-customizers --- [INFO] Changes detected - recompiling the module! :source [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] Compiling 4 source files with javac [debug target 25] to target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ context-customizers --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.ContextCustomizerFactoriesTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.257 s -- in com.logicbig.example.ContextCustomizerFactoriesTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.273 s [INFO] Finished at: 2026-02-06T11:11:15+08:00 [INFO] ------------------------------------------------------------------------
Conclusion
In this example, the test passes without declaring the custom bean in any configuration class. The bean is registered programmatically by a ContextCustomizer, which is activated through a ContextCustomizerFactory registered using @ContextCustomizerFactories.
This confirms that the test context was customized before refresh, which is precisely the responsibility of the ContextCustomizer mechanism in Spring Test.
Example ProjectDependencies and Technologies Used: - spring-context 7.0.3 (Spring Context)
Version Compatibility: 6.1.0 - 7.0.3 Version compatibilities of spring-context with this example:
- 6.1.0
- 6.1.1
- 6.1.2
- 6.1.3
- 6.1.4
- 6.1.5
- 6.1.6
- 6.1.7
- 6.1.8
- 6.1.9
- 6.1.10
- 6.1.11
- 6.1.12
- 6.1.13
- 6.1.14
- 6.1.15
- 6.1.16
- 6.1.17
- 6.1.18
- 6.1.19
- 6.1.20
- 6.1.21
- 6.2.0
- 6.2.1
- 6.2.2
- 6.2.3
- 6.2.4
- 6.2.5
- 6.2.6
- 6.2.7
- 6.2.8
- 6.2.9
- 6.2.10
- 6.2.11
- 6.2.12
- 6.2.13
- 6.2.14
- 6.2.15
- 7.0.0
- 7.0.1
- 7.0.2
- 7.0.3
Versions in green have been tested.
- spring-test 7.0.3 (Spring TestContext Framework)
- junit-jupiter 6.0.2 (Module "junit-jupiter" of JUnit)
- JDK 25
- Maven 3.9.11
|