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) |
| 传类型 + 默认 Answer | mock(Class, Answer) | 覆盖全局默认返回值策略 |
| 传类型 + MockSettings | mock(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 会在链式调用时产生大量内部代理,降低测试执行速度,建议只用于简单的对象图。
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 集合。