一、@ExtendWith
是什么?
@ExtendWith
是 JUnit 5 中用于**注册扩展(Extension)**的注解,允许将自定义或第三方扩展附加到测试类或方法上,以增强测试功能。扩展可以干预测试生命周期(如初始化、执行、清理),或提供依赖注入、条件测试等能力。
二、核心概念
1. 扩展(Extension)
- 实现特定接口(如
BeforeEachCallback
、ParameterResolver
)的类。 - 通过干预 JUnit 生命周期或提供参数解析来扩展测试行为。
2. 常用扩展接口
接口 | 说明 |
---|---|
BeforeEachCallback | 在 @BeforeEach 方法前执行逻辑 |
AfterEachCallback | 在 @AfterEach 方法后执行逻辑 |
BeforeAllCallback | 在 @BeforeAll 方法前执行逻辑 |
AfterAllCallback | 在 @AfterAll 方法后执行逻辑 |
ParameterResolver | 解析测试方法或构造函数的参数(依赖注入) |
TestExecutionCondition | 动态决定是否执行测试(条件测试) |
3. 作用域
- 类级别:通过
@ExtendWith
注解在测试类上,影响类中所有测试方法。 - 方法级别:通过
@ExtendWith
注解在测试方法上,仅影响该方法。
三、使用场景
- 依赖注入
自动注入测试所需的资源(如数据库连接、Mock 对象)。 - 条件测试
根据环境变量或配置动态跳过某些测试。 - 资源管理
自动创建和清理临时文件、目录或网络连接。 - 监控与日志
记录测试执行时间、失败信息等。 - 集成第三方框架
如 Spring 的SpringExtension
、Mockito 的MockitoExtension
。
四、使用步骤
1. 添加扩展依赖
以 Maven 为例,若使用第三方扩展(如 Mockito):
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
2. 注册扩展
在测试类或方法上添加 @ExtendWith
:
// 类级别扩展
@ExtendWith(MockitoExtension.class)
class MyTestClass {
// ...
}
// 方法级别扩展
class MyTestClass {
@Test
@ExtendWith(CustomExtension.class)
void myTest() {
// ...
}
}
五、示例
示例 1:使用 Mockito 扩展(依赖注入)
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Test
void testFindUserById() {
when(userRepository.findById(1L)).thenReturn(new User(1L, "Alice"));
UserService userService = new UserService(userRepository);
User user = userService.findUserById(1L);
Assertions.assertEquals("Alice", user.getName());
}
}
示例 2:自定义扩展(记录测试时间)
步骤 1:实现扩展类
import org.junit.jupiter.api.extension.*;
public class TimingExtension implements BeforeEachCallback, AfterEachCallback {
private long startTime;
@Override
public void beforeEach(ExtensionContext context) {
startTime = System.currentTimeMillis();
}
@Override
public void afterEach(ExtensionContext context) {
long duration = System.currentTimeMillis() - startTime;
System.out.printf("测试 %s 执行耗时: %d ms%n",
context.getDisplayName(), duration);
}
}
步骤 2:注册扩展
@ExtendWith(TimingExtension.class)
class TimingTest {
@Test
void fastTest() throws InterruptedException {
Thread.sleep(100);
}
@Test
void slowTest() throws InterruptedException {
Thread.sleep(200);
}
}
输出结果:
测试 fastTest() 执行耗时: 100 ms
测试 slowTest() 执行耗时: 200 ms
示例 3:条件扩展(跳过夜间测试)
步骤 1:实现条件判断逻辑
import org.junit.jupiter.api.extension.*;
public class DisableAtNightCondition implements TestExecutionCondition {
@Override
public ConditionEvaluationResult evaluate(TestExecutionConditionContext context) {
int hour = LocalDateTime.now().getHour();
if (hour >= 22 || hour < 6) {
return ConditionEvaluationResult.disabled("夜间不执行测试");
}
return ConditionEvaluationResult.enabled("正常执行");
}
}
步骤 2:注册扩展
@ExtendWith(DisableAtNightCondition.class)
class NightTest {
@Test
void someTest() {
Assertions.assertTrue(true);
}
}
六、高级用法
1. 组合多个扩展
@ExtendWith({MockitoExtension.class, TimingExtension.class})
class CombinedTest {
// ...
}
2. 自定义参数解析器(依赖注入)
public class DatabaseConnectionResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext paramCtx, ExtensionContext extCtx) {
return paramCtx.getParameter().getType() == DatabaseConnection.class;
}
@Override
public Object resolveParameter(ParameterContext paramCtx, ExtensionContext extCtx) {
return new DatabaseConnection("jdbc:mysql://localhost/test");
}
}
@ExtendWith(DatabaseConnectionResolver.class)
class DatabaseTest {
@Test
void testQuery(DatabaseConnection conn) {
Assertions.assertNotNull(conn.executeQuery("SELECT 1"));
}
}
七、注意事项
-
扩展执行顺序
JUnit 不保证多个扩展的执行顺序,但可通过@Order
注解控制:@ExtendWith(ExtensionA.class) @ExtendWith(ExtensionB.class) class OrderedTest { // ExtensionA 默认优先级较低,先执行 }
-
作用域冲突
避免在类和方法级别重复注册相同扩展,导致重复执行。 -
扩展与生命周期方法
扩展逻辑在@BeforeEach
/@AfterEach
之前或之后执行,具体取决于接口实现。
八、总结
@ExtendWith
的核心价值:
- 模块化:将通用逻辑(如依赖注入、资源管理)封装为可重用的扩展。
- 解耦:分离测试逻辑与辅助代码,提升可维护性。
- 灵活性:轻松集成第三方库或自定义测试策略。
推荐实践:
- 优先使用成熟的第三方扩展(如 Spring、Mockito 的扩展)。
- 在需要复用逻辑时开发自定义扩展。
- 通过组合扩展实现复杂测试需求。