A TestExecutionListener listens for the test execution events published by the TestContextManager.
Default Test ExecutionListeners
The spring-test module declares all default TestExecutionListeners in its META-INF/spring.factories properties file.
Spring.factories (spring-test version 4.3.10.RELEASE):
# Default TestExecutionListeners for the Spring TestContext Framework
#
org.springframework.test.context.TestExecutionListener = \
org.springframework.test.context.web.ServletTestExecutionListener,\
org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\
org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
.......
All above listeners have a specific responsibility in Spring integration tests execution. For example, DependencyInjectionTestExecutionListener provides support for dependency injection and initialization of test instances.
What is spring.factories?
This is a general purpose factory loading mechanism provided by Spring core. It requires the file spring.factories (a properties file) located under META-INF.
SpringFactoriesLoader.loadFactories(MyService.class, ..) method can be used to load a specified class. This method returns list of specified class instances. There might be multiple spring.factories files in different JAR files in the class path which may defined a particular type multiple times.
Note that spring-boot also uses this mechanism to load auto configurations, check out here.
The TestExecutionListener interface
Definition of TestExecutionListenerVersion: 7.0.4 package org.springframework.test.context;
public interface TestExecutionListener {
default void beforeTestClass(TestContext testContext) 1
throws Exception;
default void prepareTestInstance(TestContext testContext) 2
throws Exception;
default void beforeTestMethod(TestContext testContext) 3
throws Exception;
default void beforeTestExecution(TestContext testContext) 4
throws Exception;
default void afterTestExecution(TestContext testContext) 5
throws Exception;
default void afterTestMethod(TestContext testContext) 6
throws Exception;
default void afterTestClass(TestContext testContext) 7
throws Exception;
}
As seen above, a TestExecutionListener implementation can receive events during different test execution stages. Let's see what we can do with TestContext which is passed to the listener on each event.
The TextContext interface
TestContext contains information of spring context and of the target test class/methods as well, which can be used to alter the tests behavior or extend their functionality.
Example of Custom TestExecutionListener
Let's see how to defined our own TestExecutionListener to understand how it works.
Creating a simple Spring application
package com.logicbig.example;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
public void doSomething() {
System.out.println("-- in MyBean.doSomething() method --");
}
}
package com.logicbig.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@ComponentScan
public class AppConfig {
}
Writing a TestExecutionListener
package com.logicbig.example;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
public class MyListener implements TestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("MyListener.beforeTestClass()");
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
System.out.println("MyListener.prepareTestInstance()");
}
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
System.out.println("MyListener.beforeTestMethod()");
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
System.out.println("MyListener.afterTestMethod()");
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
System.out.println("MyListener.afterTestClass");
}
}
Writing JUnit test
A TextExecutionListener can be registered via @TestExecutionListeners annotation:
package com.logicbig.example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
@TestExecutionListeners(value = {MyListener.class,
DependencyInjectionTestExecutionListener.class})
public class MyTests {
@Autowired
private MyBean myBean;
@Test
public void testDoSomething() {
myBean.doSomething();
assertTrue(true);
}
}
Output$ mvn test [INFO] Scanning for projects... [INFO] [INFO] --------< com.logicbig.example:custom-test-execution-listener >--------- [INFO] Building custom-test-execution-listener 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ custom-test-execution-listener --- [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\custom-test-execution-listener\src\main\resources [INFO] [INFO] --- compiler:3.3:compile (default-compile) @ custom-test-execution-listener --- [INFO] Changes detected - recompiling the module! [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] Compiling 2 source files to D:\example-projects\spring-core-testing\custom-test-execution-listener\target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ custom-test-execution-listener --- [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\custom-test-execution-listener\src\test\resources [INFO] [INFO] --- compiler:3.3:testCompile (default-testCompile) @ custom-test-execution-listener --- [INFO] Changes detected - recompiling the module! [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] Compiling 2 source files to D:\example-projects\spring-core-testing\custom-test-execution-listener\target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ custom-test-execution-listener --- [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.MyTests MyListener.beforeTestClass() MyListener.prepareTestInstance() MyListener.beforeTestMethod() -- in MyBean.doSomething() method -- MyListener.afterTestMethod() MyListener.afterTestClass [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.468 s -- in com.logicbig.example.MyTests [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 5.535 s [INFO] Finished at: 2026-02-06T11:01:38+08:00 [INFO] ------------------------------------------------------------------------
Note that, we also used DependencyInjectionTestExecutionListener in above example, that's because when we specify our own listeners with @TestExecutionListeners, all default listeners are overridden. In above example, We needed DependencyInjectionTestExecutionListener as we used @Autowired.
Merging TestExecutionListeners
As mentioned above, when we specify our own listeners with @TestExecutionListeners, all default listeners are overridden. One way to utilize default listeners is to include them manually in the value attribute of this annotation (as we saw above). Another way is to use 'mergeMode' of this annotation, which will not replace the default listeners. The following snippet shows the usage:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
@TestExecutionListeners(value = MyListener.class,
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public class MyTests {
....
}
The default value of 'mergeMode' is MergeMode.REPLACE_DEFAULTS.
Example ProjectDependencies and Technologies Used: - spring-context 7.0.4 (Spring Context)
Version Compatibility: 5.0.0.RELEASE - 7.0.4 Version compatibilities of spring-context with this example: Versions in green have been tested.
- spring-test 7.0.4 (Spring TestContext Framework)
- junit-jupiter-engine 6.0.2 (Module "junit-jupiter-engine" of JUnit)
- JDK 25
- Maven 3.9.11
|