4. 使用Mockito创建Mock对象:mock()、@Mock注解、默认行为配置

Mockito 是 Java 生态中最流行的 Mock 框架,其核心 API 提供了三种方式来创建 Mock 对象:静态方法 mock()、注解 @Mock 配合初始化器、以及通过 Rule/Extension 自动初始化。理解这三种方式的差异,以及 Mock 对象的默认行为,是编写可靠单元测试的基石。

核心要点:Mock 对象在未被显式配置时,所有方法调用都返回“空值”——基本类型返回 0/false,对象类型返回 null,集合返回空集合,void 方法什么也不做。这一默认行为确保测试不会因为 Mock 对象意外的副作用而失败。

4.1 mock() 静态方法

Mockito.mock(Class<T> classToMock) 是最直接的创建方式,在需要 Mock 的对象数量较少或动态确定时非常灵活。

import static org.mockito.Mockito.mock;

// 创建一个 UserService 的 Mock 对象
UserService userService = mock(UserService.class);

// 验证未配置时返回空
User user = userService.findById(1L);
System.out.println(user); // 输出 null

重载版本允许传入 MockSettings 来定制 Mock 的名称、默认答案、序列化支持等。

import org.mockito.MockSettings;
import static org.mockito.Mockito.withSettings;

MockSettings settings = withSettings()
    .name("customUserServiceMock")
    .defaultAnswer(Mockito.RETURNS_SMART_NULLS);

UserService smartMock = mock(UserService.class, settings);
参数场景方法签名说明
只传类型mock(Class)使用默认 Answer(RETURNS_DEFAULTS)
传类型 + 默认 Answermock(Class, Answer)覆盖全局默认返回值策略
传类型 + MockSettingsmock(Class, MockSettings)定制名称、日志、序列化等高级选项

4.2 @Mock 注解 + 初始化器

当测试类中有多个 Mock 时,注解方式减少重复代码,提高可读性。但必须调用初始化器才能生效。

public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailService emailService;

    @BeforeEach
    public void setUp() {
        // 方式A:使用 MockitoAnnotations
        MockitoAnnotations.openMocks(this);

        // 方式B:使用 MockitoJUnitRunner(JUnit4)
        // 方式C:使用 MockitoExtension(JUnit5)
    }

    @Test
    public void testRegisterUser() {
        // 直接使用 @Mock 对象,无需手动创建
        when(userRepository.findByEmail(anyString())).thenReturn(null);
        // ...
    }
}

JUnit5 推荐:MockitoExtension

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @Test
    public void test() {
        // 自动初始化,无需 setUp 方法
    }
}

JUnit4 的 MockitoJUnitRunner

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    // ...
}
最佳实践:所有通过 @Mock 注解声明的字段,Mockito 会为每个字段自动调用 mock() 方法。但如果测试类还需要 @InjectMocks(第10章详解),必须确保 openMocks 或 MockitoExtension 在注入之前被调用。

4.3 默认行为与配置

Mockito 提供多个内置 Answer 实现来控制 Mock 的默认行为。以下是最常见的四种:

  • RETURNS_DEFAULTS:默认策略,返回空值(null、0、false、空集合)
  • RETURNS_SMART_NULLS:返回“智能 null”,当代码试图使用 null 对象时会抛出更清晰的异常(如 SmartNullPointerException),有助于快速定位未 Stub 的调用
  • RETURNS_DEEP_STUBS:允许链式调用自动 Stub,例如 mock.getA().getB().getName(),但易导致过度依赖内部结构,不推荐
  • RETURNS_MOCKS:对返回对象类型的方法自动返回一个 Mock 而非 null,适用于接口型返回

全局配置(通过 MockSettings)或局部配置(通过 mock(Class, Answer))后,所有未显式 Stub 的方法都会按该 Answer 执行。

// 使用 RETURNS_SMART_NULLS
UserService smartMock = mock(UserService.class, RETURNS_SMART_NULLS);

// 调用未 Stub 的方法
User user = smartMock.findById(1L); // 返回 SmartNull
user.getEmail(); // 抛出 SmartNullPointerException

4.4 三种创建方式对比

特性mock()@Mock + openMocks@Mock + Extension/Runner
代码量多(每次手动创建)较少(需 setUp 调用一次)最少(自动初始化)
动态性高(可在循环/条件中创建)中(编译期声明)中(编译期声明)
需要额外库只依赖 mockito-core只依赖 mockito-core需要 junit-jupiter (JUnit5) 或 junit (JUnit4)
初始化时机即时调用 openMocks 时测试生命周期前
可读性中高(注解声明清晰)高 + 简化
最适合场景单/少量 Mock,或参数化 Factory中等数量 Mock,传统 JUnit4 项目JUnit5 项目,团队规范统一

4.5 调试与常见错误

  • 错误:Mock 未初始化
    表现:调用时抛出 NullPointerException 或 MockitoException: fields annotated with @Mock were not initialized
    解决:检查是否调用了 MockitoAnnotations.openMocks(this),或使用 MockitoExtension。
  • 错误:误用 @InjectMocks 导致 Mock 被覆盖
    解决:确保 @InjectMocks 的范围——它只会注入用 @Mock 或 @Spy 声明的对象,不会影响手动通过 mock() 创建的实例。
  • 调试技巧:打印 Mock 名称
    在 MockSettings 中指定名称,错误信息会包含该名称,便于定位测试失败原因。
// 使用名称帮助定位
UserService namedMock = mock(UserService.class, withSettings().name("userService4Test"));
// 当验证失败时,异常消息会显示 "userService4Test.findById"
性能注意:大量使用 @Mock 注解(如超过50个)可能会略微增加测试初始化时间(约几十毫秒)。如果测试类非常大,考虑拆分测试或使用 mock() 按需创建。此外,RETURNS_DEEP_STUBS 会在链式调用时产生大量内部代理,降低测试执行速度,建议只用于简单的对象图。
三种 Mock 创建方式与默认行为 mock(UserService.class) 返回 Mock 实例 默认 Answer:RETURNS_DEFAULTS @Mock UserRepository MockitoAnnotations.openMocks(this) 自动初始化所有 @Mock 字段 @ExtendWith(MockitoExtension.class) 生命周期回调自动初始化 无需额外 setUp 代码 默认行为配置(when-thenReturn 等) 覆盖/增强默认返回值 关键区别:mock() 需要在每处测试中手动调用; @Mock 注解依赖初始化机制(openMocks/Extension/Runner);默认 Answer 控制未 Stub 方法的行为。

4.6 默认行为配置实战:设置自定义默认 Answer

当不希望测试中出现 null 导致下游调用 NPE 时,可以为整个测试类设置统一的默认 Answer。

public class OrderServiceTest {

    @Mock(answer = Answers.RETURNS_SMART_NULLS)
    private OrderRepository orderRepository;

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private InventoryClient inventoryClient;

    // 使用 @InjectMocks 将 Mock 注入被测对象
    @InjectMocks
    private OrderService orderService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testGetOrder() {
        // orderRepository 默认返回 SmartNull,调用任何方法都会生成清晰异常
        // inventoryClient 默认返回 Mock,可以链式调用
        // 但建议只对稳定的内部接口使用 deep stubs
    }
}
  • RETURNS_SMART_NULLS:适用于大多数业务服务层 Mock,帮助发现遗漏的 Stub。
  • RETURNS_DEEP_STUBS:仅在链式调用非常频繁且结构稳定时使用,否则会使测试与内部实现过度耦合。
  • RETURNS_MOCKS:适用于返回接口类型的方法,避免 null 导致的 NullPointerException。

4.7 二次配置:通过 MockSettings 精细控制

MockSettings customSettings = withSettings()
    .name("strictMock")
    .defaultAnswer(RETURNS_SMART_NULLS)
    .verboseLogging()          // 每次交互都输出日志(方便调试)
    .serializable()            // 支持序列化(远程测试等场景)
    .useConstructor()          // 使用类的构造器创建(很少用)
    .outerInstance(this);      // 用于内部类 Mock

UserService customMock = mock(UserService.class, customSettings);

在调试阶段,verboseLogging() 可以帮助追踪每次方法调用的参数和返回结果。

生产环境测试:建议在 CI 中关闭 verbose logging 和 RETURNS_SMART_NULLS(改用 RETURNS_DEFAULTS),避免日志污染和性能开销。开发阶段可以开启辅助排查。

4.8 小结

  • mock() 方法是最灵活的创建方式,适合动态场景。
  • @Mock 注解配合 JUnit Extension 是最推荐的方式,代码简洁且结构清晰。
  • 默认行为通过 Answer 配置,常用 RETURNS_DEFAULTS 和 RETURNS_SMART_NULLS。
  • 调试时通过 MockSettings.name() 和 verboseLogging() 定位问题。
  • 性能上注意避免 RETURNS_DEEP_STUBS 和过大的 Mock 集合。