TestExecutionListener` 是 Spring Test 的核心扩展接口,允许开发者监听测试执行的生命周期事件,并插入自定义逻辑。通过实现此接口,可以干预测试的 准备阶段、执行阶段 和 清理阶段,适用于监控、资源管理、日志记录等场景。
接口定义了以下关键方法(均为默认方法,可按需重写):
方法名 | 触发时机 | 典型用途 |
---|---|---|
beforeTestClass |
测试类开始执行前 | 初始化全局资源(如启动 Docker 容器) |
prepareTestInstance |
测试实例创建后,依赖注入前 | 修改测试实例属性 |
beforeTestMethod |
每个 @Test 方法执行前 |
准备测试数据 |
beforeTestExecution |
@Test 方法执行前(在 @BeforeEach 之后) |
最后时刻的预处理 |
afterTestExecution |
@Test 方法执行后(在 @AfterEach 之前) |
收集测试结果或清理临时资源 |
afterTestMethod |
每个 @Test 方法执行后 |
清理测试数据 |
afterTestClass |
测试类所有方法执行完毕 | 释放全局资源(如关闭连接) |
目标:在控制台输出每个测试方法的执行耗时。
public class TimingExecutionListener implements TestExecutionListener {
private Map<String, Long> startTimes = new HashMap<>();
@Override
public void beforeTestExecution(TestContext testContext) {
String testName = testContext.getTestMethod().getName();
startTimes.put(testName, System.currentTimeMillis());
}
@Override
public void afterTestExecution(TestContext testContext) {
String testName = testContext.getTestMethod().getName();
long duration = System.currentTimeMillis() - startTimes.get(testName);
System.out.printf("测试方法 [%s] 执行耗时: %d ms%n", testName, duration);
}
}
目标:在每个测试方法结束后删除临时文件。
public class TempFileCleanupListener implements TestExecutionListener {
@Override
public void afterTestExecution(TestContext testContext) {
Path tempDir = Paths.get("temp");
if (Files.exists(tempDir)) {
try {
Files.walk(tempDir)
.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println("已清理临时目录: " + tempDir);
} catch (IOException e) {
throw new RuntimeException("清理临时文件失败", e);
}
}
}
}
通过 @TestExecutionListeners
注解将监听器绑定到测试类。
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
@TestExecutionListeners(
listeners = {
TimingExecutionListener.class,
TempFileCleanupListener.class
},
mergeMode = MergeMode.MERGE_WITH_DEFAULTS // 保留默认监听器(如依赖注入、事务管理)
)
public class CustomListenerTest {
@Test
void testMethod1() throws InterruptedException {
Thread.sleep(500);
}
@Test
void testMethod2() throws InterruptedException {
// 创建临时文件(示例)
Path tempFile = Paths.get("temp/test.txt");
Files.createDirectories(tempFile.getParent());
Files.write(tempFile, "Hello".getBytes());
Thread.sleep(300);
}
}
控制台输出:
测试方法 [testMethod1] 执行耗时: 502 ms
已清理临时目录: temp
测试方法 [testMethod2] 执行耗时: 301 ms
Spring Test 默认注册了多个核心监听器,按以下顺序执行:
DependencyInjectionTestExecutionListener
:处理依赖注入。DirtiesContextTestExecutionListener
:处理 @DirtiesContext
注解。TransactionalTestExecutionListener
:管理事务。自定义监听器的执行顺序:
@TestExecutionListeners
的 listeners
数组顺序控制。mergeMode = MERGE_WITH_DEFAULTS
)。在测试方法执行前,自动模拟外部服务(如 HTTP API),并在测试后重置。
public class MockExternalServiceListener implements TestExecutionListener {
private MockWebServer mockServer;
@Override
public void beforeTestClass(TestContext testContext) {
// 启动模拟服务器
mockServer = new MockWebServer();
mockServer.start();
// 将模拟 URL 注入到 Spring 环境
String mockUrl = mockServer.url("/").toString();
testContext.getApplicationContext()
.getEnvironment()
.getPropertySources()
.addFirst(new MapPropertySource("mock", Map.of("external.service.url", mockUrl)));
}
@Override
public void beforeTestMethod(TestContext testContext) {
// 根据测试方法配置模拟响应
String methodName = testContext.getTestMethod().getName();
if ("testExternalService".equals(methodName)) {
mockServer.enqueue(new MockResponse().setBody("Mock Response"));
}
}
@Override
public void afterTestClass(TestContext testContext) {
// 关闭模拟服务器
try {
mockServer.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
@TestExecutionListeners(
listeners = MockExternalServiceListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS
)
public class ExternalServiceTest {
@Autowired
private ExternalServiceClient client;
@Test
void testExternalService() {
String response = client.callExternalService();
assertEquals("Mock Response", response);
}
}
order
属性或数组顺序控制。通过 TestExecutionListener
,开发者可以深度定制 Spring 测试流程,实现诸如 性能监控、环境隔离、动态 Mock 等高级功能,显著提升测试的灵活性和可维护性。