@ExtendWith 是 JUnit 5 启用自定义扩展的核心机制,通过实现 Extension 接口(如 AroundTestExecutionCallback)在测试执行前后插入逻辑,支持耗时统计、事务管理等拦截式功能,并可通过注解、配置和上下文存储实现复用与参数化。

在 JUnit 5 中,@ExtendWith 是启用自定义扩展的核心机制,它本身不提供“拦截器”概念,但可通过实现 Extension 接口(尤其是 BeforeEachCallback、AfterEachCallback、AroundTestExecutionCallback 等)来达到类似 AOP 的拦截效果——即在测试方法执行前后或环绕时插入自定义逻辑。
定义一个可复用的测试拦截扩展
比如你想在每个测试方法运行前自动启动数据库事务、运行后回滚,或记录执行耗时。这不是靠注解“拦截”,而是通过扩展生命周期回调实现:
- 实现
AroundTestExecutionCallback可完全控制测试方法的执行(类似 try-finally) - 实现
BeforeEachCallback和AfterEachCallback更轻量,适合前置准备/后置清理 - 扩展类需是 public、无参构造,JUnit 才能实例化它
编写一个耗时统计拦截器示例
下面是一个统计单个测试方法执行时间的简单扩展:
public class TimingExtension implements AroundTestExecutionCallback {
@Override
public void aroundTestExecution(Executable executable) throws Throwable {
long start = System.nanoTime();
try {
executable.execute(); // 实际执行测试方法
} finally {
long durationMs = (System.nanoTime() - start) / 1_000_000;
String testName = getCurrentTestName();
System.out.printf("[Timing] %s took %d ms%n", testName, durationMs);
}
}
<pre class="brush:php;toolbar:false;">private String getCurrentTestName() {
ExtensionContext.Store store = ExtensionContext.currentContext()
.getStore(ExtensionContext.Namespace.create(getClass()));
return store.get("testDisplayName", String.class);
}}
立即学习“Java免费学习笔记(深入)”;
注意:要获取当前测试名,需配合 TestInstancePostProcessor 或在 TestWatcher 中提前存入上下文 store(更健壮的做法是结合 TestWatcher + Store)。
组合多个扩展并支持配置化
实际项目中常需参数化行为(如只对特定标签开启计时)。可通过 ParameterResolver 注入配置,或使用静态配置 + 注解驱动:
- 定义自定义注解:
@EnableTiming标记需要统计的测试类或方法 - 在
supportsTestTemplate或aroundTestExecution中检查ExtensionContext是否有该注解 - 用
ExtensionContext.getRequiredTestMethod().getAnnotation(EnableTiming.class)判断是否启用 - 多个扩展可同时使用:
@ExtendWith({TimingExtension.class, DbTransactionExtension.class})
注册扩展的三种方式
扩展生效不依赖 Spring 或外部容器,纯 JUnit 原生支持:
-
类级别:在测试类上加
@ExtendWith(MyExtension.class) -
方法级别:仅对某个
@Test方法生效 -
全局注册:在
src/test/resources/junit-platform.properties中写junit.jupiter.extensions.autoload=true并确保扩展类在META-INF/services/org.junit.jupiter.api.extension.Extension文件中声明全限定名
不复杂但容易忽略的是上下文隔离——每个测试方法都有独立的 ExtensionContext 实例,跨方法共享状态需谨慎(如用 static 字段可能引发并发问题),推荐用 ExtensionContext.Store 绑定到当前节点。


















