ExtendWith注解

一、@ExtendWith 是什么?

@ExtendWith 是 JUnit 5 中用于**注册扩展(Extension)**的注解,允许将自定义或第三方扩展附加到测试类或方法上,以增强测试功能。扩展可以干预测试生命周期(如初始化、执行、清理),或提供依赖注入、条件测试等能力。


二、核心概念

1. 扩展(Extension)
  • 实现特定接口(如 BeforeEachCallbackParameterResolver)的类。
  • 通过干预 JUnit 生命周期或提供参数解析来扩展测试行为。
2. 常用扩展接口
接口说明
BeforeEachCallback@BeforeEach 方法前执行逻辑
AfterEachCallback@AfterEach 方法后执行逻辑
BeforeAllCallback@BeforeAll 方法前执行逻辑
AfterAllCallback@AfterAll 方法后执行逻辑
ParameterResolver解析测试方法或构造函数的参数(依赖注入)
TestExecutionCondition动态决定是否执行测试(条件测试)
3. 作用域
  • 类级别:通过 @ExtendWith 注解在测试类上,影响类中所有测试方法。
  • 方法级别:通过 @ExtendWith 注解在测试方法上,仅影响该方法。

三、使用场景

  1. 依赖注入
    自动注入测试所需的资源(如数据库连接、Mock 对象)。
  2. 条件测试
    根据环境变量或配置动态跳过某些测试。
  3. 资源管理
    自动创建和清理临时文件、目录或网络连接。
  4. 监控与日志
    记录测试执行时间、失败信息等。
  5. 集成第三方框架
    如 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"));
    }
}

七、注意事项

  1. 扩展执行顺序
    JUnit 不保证多个扩展的执行顺序,但可通过 @Order 注解控制:

    @ExtendWith(ExtensionA.class)
    @ExtendWith(ExtensionB.class)
    class OrderedTest {
        // ExtensionA 默认优先级较低,先执行
    }
    
  2. 作用域冲突
    避免在类和方法级别重复注册相同扩展,导致重复执行。

  3. 扩展与生命周期方法
    扩展逻辑在 @BeforeEach/@AfterEach 之前或之后执行,具体取决于接口实现。


八、总结

@ExtendWith 的核心价值

  • 模块化:将通用逻辑(如依赖注入、资源管理)封装为可重用的扩展。
  • 解耦:分离测试逻辑与辅助代码,提升可维护性。
  • 灵活性:轻松集成第三方库或自定义测试策略。

推荐实践

  • 优先使用成熟的第三方扩展(如 Spring、Mockito 的扩展)。
  • 在需要复用逻辑时开发自定义扩展。
  • 通过组合扩展实现复杂测试需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值