当前位置: 首页 > news >正文

2.5 使用注解进行单元测试详解


Mockito 使用注解进行单元测试详解

Mockito 提供了一系列注解来简化测试代码的编写,减少手动创建和管理 Mock 对象的样板代码。结合 JUnit 5,可以更高效地构建清晰、易维护的单元测试。


1. 核心注解概览
注解作用
@Mock创建并注入一个 Mock 对象(完全模拟,方法默认返回空或默认值)。
@Spy创建并注入一个 Spy 对象(部分模拟,默认调用真实方法,除非显式覆盖)。
@InjectMocks自动将 @Mock@Spy 对象注入到被测类中(依赖注入)。
@Captor自动初始化 ArgumentCaptor,用于捕获方法参数。
@ExtendWith启用 Mockito 扩展(JUnit 5 必需),替代旧版 @RunWith

2. 注解配置与启用
2.1 启用 Mockito 支持

在测试类上添加 @ExtendWith(MockitoExtension.class),激活 Mockito 注解功能:

@ExtendWith(MockitoExtension.class) // JUnit 5 必加
public class UserServiceTest {
    // 测试代码...
}
2.2 自动初始化注解

无需手动调用 MockitoAnnotations.openMocks(this)@ExtendWith 已自动处理。


3. 注解使用详解
3.1 @Mock 注解

作用:创建完全模拟的依赖对象。

示例场景

public class UserService {
    private final UserDao userDao;
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    
    public User getUserById(int id) {
        return userDao.findById(id);
    }
}

测试代码

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserDao mockUserDao; // 自动创建 Mock 对象

    @InjectMocks
    private UserService userService; // 自动注入 mockUserDao

    @Test
    void getUserById_ShouldReturnUser() {
        // 配置 Mock 行为
        when(mockUserDao.findById(1)).thenReturn(new User(1, "Alice"));

        // 调用被测方法
        User user = userService.getUserById(1);

        // 验证结果
        assertEquals("Alice", user.getName());
        verify(mockUserDao).findById(1);
    }
}
3.2 @Spy 注解

作用:创建部分模拟对象,保留真实方法逻辑,除非显式覆盖。

示例场景

public class PaymentService {
    public boolean validateCard(String cardNumber) {
        return cardNumber != null && cardNumber.length() == 16;
    }

    public boolean processPayment(String cardNumber) {
        if (!validateCard(cardNumber)) return false;
        // 真实支付逻辑...
        return true;
    }
}

测试代码

@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {

    @Spy // 部分模拟,保留真实方法
    private PaymentService spyPaymentService;

    @Test
    void processPayment_ShouldUseMockedValidation() {
        // 覆盖 validateCard 方法
        doReturn(true).when(spyPaymentService).validateCard(anyString());

        // 调用被测方法(processPayment 会调用被覆盖的 validateCard)
        boolean result = spyPaymentService.processPayment("invalid_card");

        assertTrue(result);
        verify(spyPaymentService).validateCard("invalid_card");
    }
}
3.3 @InjectMocks 注解

作用:自动将 @Mock@Spy 对象注入到被测类中。

注入规则

  1. 构造器注入(优先):匹配参数类型和数量。
  2. Setter 注入:调用 setter 方法。
  3. 字段注入(最后):直接反射注入字段。

示例

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private InventoryService inventoryService;

    @Mock
    private PaymentService paymentService;

    @InjectMocks // 自动注入 inventoryService 和 paymentService
    private OrderService orderService;

    @Test
    void placeOrder_ShouldCheckInventory() {
        when(inventoryService.checkStock(anyString())).thenReturn(true);
        orderService.placeOrder("product_123");
        verify(inventoryService).checkStock("product_123");
    }
}
3.4 @Captor 注解

作用:自动创建参数捕获器,简化参数验证。

示例

@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {

    @Mock
    private EmailClient mockEmailClient;

    @InjectMocks
    private NotificationService notificationService;

    @Captor // 自动初始化 ArgumentCaptor
    private ArgumentCaptor<EmailRequest> emailCaptor;

    @Test
    void sendWelcomeEmail_ShouldCaptureEmailContent() {
        notificationService.sendWelcomeEmail("user@example.com");

        verify(mockEmailClient).send(emailCaptor.capture());
        EmailRequest captured = emailCaptor.getValue();
        
        assertEquals("user@example.com", captured.getTo());
        assertTrue(captured.getSubject().contains("Welcome"));
    }
}

4. 常见问题与解决方案
问题解决方案
@Mock 对象为 null检查是否添加 @ExtendWith(MockitoExtension.class)
依赖注入失败确保 @InjectMocks 类的依赖项有对应的 @Mock@Spy 对象。
Spy 对象调用真实方法导致异常使用 doReturn().when() 替代 when().thenReturn() 避免执行真实方法。
参数捕获器未初始化使用 @Captor 替代手动创建 ArgumentCaptor

5. 高级整合:与 Spring Boot 测试结合

在 Spring Boot 测试中,可使用 @MockBean 替换容器中的 Bean:

@SpringBootTest
public class ProductServiceIntegrationTest {

    @MockBean // Spring 管理的 Mock
    private InventoryService mockInventoryService;

    @Autowired
    private ProductService productService;

    @Test
    void reserveProduct_ShouldUseMockInventory() {
        when(mockInventoryService.reserve(anyString())).thenReturn(true);
        boolean result = productService.reserveProduct("product_123");
        assertTrue(result);
    }
}

6. 最佳实践
  1. 保持测试简洁:使用注解减少手动初始化代码。
  2. 明确依赖关系:通过 @InjectMocks 明确被测类的依赖注入方式。
  3. 避免过度 Mock:仅 Mock 外部依赖,保留核心逻辑的真实性。
  4. 结合 AssertJ:使用流式断言提高测试可读性:
    assertThat(capturedEmail.getSubject()).contains("Welcome");
    

通过合理使用 Mockito 注解,可以显著提升单元测试的编写效率和可维护性。

相关文章:

  • 【vue3】实现pdf在线预览的几种方式
  • 论软件评估方法
  • rv1103b编译opencv
  • 家里装修想用投影仪,如何选择?装修中应该注意什么?
  • 浮点数二分
  • 数智化的力量:柏强制药构建医药高质量发展的新引擎
  • 场外个股期权下单后多久成交?场外个股期权对投资组合的影响
  • 《pyqt+open3d》第三章——icp配准点对面——稳健性提升
  • 【前端框架】Vue3 中 `setup` 函数的作用和使用方式
  • 【Elasticsearch】Token Graphs
  • 【LeetCode】739. 每日温度
  • rust学习二、入门之运行单个脚本
  • vue前端可视化大屏页面适配方案
  • 日本90年代经济泡沫初期是什么情况?
  • 面试经典150题——字典树
  • 深入探索xtquant:账户信息查询的全面指南
  • WPS中如何批量上下居中对齐word表格中的所有文字
  • DeepSeek的深度解析:由来、研发过程、公司背景、优势、劣势与总结
  • WSL Ubuntu 安装 CUDA 教程
  • 嵌入式AI革命:DeepSeek开源如何终结GPU霸权,开启单片机智能新时代?
  • 商业网站的建设/做网络推广好吗
  • 网站怎么做交易/网站的排名优化怎么做
  • 太湖云建站网站建设/2023b站免费推广入口
  • 网站设计搜索栏怎么做/互联网电商平台
  • 塑料机械怎么做网站/指数搜索
  • 网站怎样建设友情链接/南宁网络推广外包