第3章 Mockito基本使用:@Mock、@InjectMocks、@Spy、@Captor注解及MockitoAnnotations.openMocks

3.1 核心注解概述

Mockito 提供一组声明式注解,用于简化 mock 对象的创建、注入和参数捕获。本章聚焦四个最常用的注解及其初始化方式,帮助开发者在 Worker 单元测试中快速搭建 mock 环境。

注解用途适用场景
@Mock创建 mock 对象,所有方法返回默认值被测试类依赖的外部服务、DAO、工具类
@InjectMocks自动将 @Mock 或 @Spy 对象注入到被测试实例被测试类(如 Worker)内部依赖的自动装配
@Spy创建 spy(部分 mock),保留真实方法行为,可选择性 stub需要保留部分真实逻辑的复杂对象
@Captor在验证阶段捕获方法调用时传入的参数断言内部调用参数是否符合预期

3.2 注解初始化方式

使用这些注解前必须调用初始化方法,否则注解不会生效。三种主流初始化方式:

  1. MockitoAnnotations.openMocks(this) —— 推荐方式,返回 AutoCloseable,可在 @BeforeEach 中调用。
  2. MockitoRule —— JUnit 4 风格,通过 @Rule 自动初始化。
  3. MockitoExtension —— JUnit 5 官方扩展,通过 @ExtendWith 声明。
实践建议: 在 JUnit 5 项目中优先使用 MockitoExtension;若需手动控制生命周期,使用 MockitoAnnotations.openMocks(this) 并在 @AfterEach 中 close()。

3.3 代码示例:Worker 测试中的注解使用

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class WorkerServiceTest {

    @Mock
    private TaskRepository taskRepository;

    @Mock
    private NotificationClient notificationClient;

    @InjectMocks
    private WorkerService workerService;

    @Captor
    private ArgumentCaptor<Task> taskCaptor;

    @Test
    void testProcessTask_success() {
        // given
        Task inputTask = new Task("ID-001", "compile");
        Mockito.when(taskRepository.findById("ID-001")).thenReturn(inputTask);

        // when
        workerService.processTask("ID-001");

        // then – 验证通知发送并捕获参数
        Mockito.verify(notificationClient).sendNotification(taskCaptor.capture());
        Task capturedTask = taskCaptor.getValue();
        Assertions.assertEquals("compile", capturedTask.getAction());
    }
}

3.4 注解交互流程

下图展示了 @InjectMocks 如何将 @Mock 和 @Spy 注入到被测试对象,以及 @Captor 在验证阶段捕获参数的完整数据流:

Mockito 注解在 Worker 测试中的数据流动 @Mock TaskRepository @Spy LoggerService @InjectMocks WorkerService (自动注入 mock/spy) @Captor ArgumentCaptor verify + captor.capture() 使用流程总结 ① 声明 @Mock/@Spy → ② @InjectMocks 注入到被测类 ③ 执行被测方法 → ④ @Captor 捕获参数并断言

3.5 调试与验证技巧

  • 验证注解是否被初始化: 在 @BeforeEach 或测试方法中加断点,观察 taskRepository 是否为 mock 实例(Mockito.mock(TaskRepository.class))。
  • 检查注入失败: 如果 @InjectMocks 注入失败,Mockito 会抛出 MockitoException,通常提示“Could not inject mocks”。检查被测试类的构造函数或 setter 是否匹配 mock 类型。
  • 使用 Mockito.validateMockitoUsage(): 在测试类 tearDown 中调用一次,可检测未使用的 stub 或未完成的验证。

3.6 常见错误及解决

错误现象根因解决方案
MockitoException: Cannot create mock for class目标类为 final / 不可 mock使用 Mockito.mock(…, Mockito.withSettings().lenient()) 或升级 Mockito 版本
@InjectMocks 注入 null被测试类没有无参构造,且 mock 类型不匹配显式添加构造器注入或字段注入的 setter
ArgumentCaptor 捕获时 ClassCastException泛型擦除导致类型不匹配使用 @Captor 注解时必须声明泛型类型,且 verify 时使用 captor.capture()
注解不生效,mock 对象为 null未调用 MockitoAnnotations.openMocks(this) 或未添加 @ExtendWith检查 JUnit 版本并正确配置初始化方式
性能提示: 在每个测试方法前初始化大量 mock 对象会影响速度。如果测试类中有超过 5 个 @Mock,考虑使用 MockitoSession 或只实例化必要的 mock。同时避免在 @BeforeEach 中做过多的 stub 设置(尤其是数据库调用),推荐每个测试方法只 stub 当前用例所需的方法。

3.7 最佳实践与稳定性建议

  • 优先使用 @ExtendWith(MockitoExtension.class): 它会在每个测试方法执行前自动打开 mock,并在方法结束后清理,无需手动 close()。
  • 避免过度使用 @Spy: spy 会保留真实行为,容易因 side effect 导致测试不稳定。能用 @Mock 替代的场景尽量用 mock。
  • 每个测试只使用一个 @Captor: 捕获多个参数时使用多个 @Captor 实例,不要重复使用同一个 captor。
  • 显式声明 Mockito.lenient(): 对于不会被所有测试方法调用的 stub,使用 lenient() 避免 UnnecessaryStubbingException。