第4章:Stubbing 方法 —— when().thenReturn()、doReturn().when()、thenThrow、thenAnswer、thenCallRealMethod
4.1 Stubbing 核心原理
Mockito 的 Stubbing(桩方法)用于预先定义 mock 对象的方法在特定参数下的返回值、抛出的异常或执行的自定义逻辑。其本质是建立一个 方法签名→行为 的映射表,在测试运行时拦截对 mock 方法的调用并返回预设结果。
| 方法 | 适用场景 | 返回值类型 | void 方法支持 |
|---|---|---|---|
when().thenReturn() |
标准有返回值方法 | 任意 | ❌ |
doReturn().when() |
void 方法、spy 对象、避免真实调用 | 任意 | ✅ |
thenThrow |
方法调用时抛出异常 | 异常子类(Throwable) | 通过 doThrow().when() |
thenAnswer |
需要根据参数或上下文动态返回结果 | Object(通过 Answer 接口) | 通过 doAnswer().when() |
thenCallRealMethod |
部分模拟(Partial Mock),调用真实实现 | 真实方法返回值 | 通过 doCallRealMethod().when() |
关键区别:
when().thenXxx() 形式在方法调用前就计算了参数表达式,因此不适合 void 方法或 spy 对象;doXxx().when() 则延迟执行,适用于所有情况,是更安全的通用写法。
4.2 实现流程与代码示例
4.2.1 标准桩:when().thenReturn()
// 模拟 Worker 类的 doWork 方法返回固定值
Worker mockWorker = mock(Worker.class);
when(mockWorker.doWork(anyString())).thenReturn("success");
String result = mockWorker.doWork("task-1");
assertEquals("success", result);
- 仅适用于 非 void 方法
- 支持链式调用:
when().thenReturn(a).thenReturn(b)按顺序返回
4.2.2 安全桩:doReturn().when()
// 对 void 方法或 spy 对象使用
Worker spyWorker = spy(new Worker());
doReturn("mocked").when(spyWorker).doWork("special");
// 等价于 when().thenReturn(),但可避免真实调用
// 对 spy 对象,doReturn 不会触发真实方法执行
assertDoesNotThrow(() -> doReturn(null).when(spyWorker).voidMethod());
4.2.3 异常桩:thenThrow
// 抛出运行时异常
when(mockWorker.checkStatus(anyInt())).thenThrow(new RuntimeException("DB down"));
// 连续抛出不同异常
when(mockWorker.process(any())).thenThrow(IOException.class, SQLException.class);
注意:如果方法签名声明了 checked 异常,则必须抛出该异常或其子类;否则编译错误。
4.2.4 动态桩:thenAnswer
// 根据调用参数动态返回
when(mockWorker.calculate(anyInt())).thenAnswer(invocation -> {
int input = invocation.getArgument(0);
return input * 10;
});
assertEquals(50, mockWorker.calculate(5));
InvocationOnMock 接口提供 getArgument(index)、getMethod()、getMock() 等反射能力。
4.2.5 部分模拟:thenCallRealMethod
// 仅对指定方法调用真实逻辑,其他方法保持 mock
Worker partialMock = mock(Worker.class);
when(partialMock.doWork("real")).thenCallRealMethod();
when(partialMock.doWork("mock")).thenReturn("mocked");
// doWork("real") 执行真实代码,doWork("mock") 返回桩值
4.3 调试与常见错误
| 常见错误 | 原因与解决 |
|---|---|
when()参数必须是对mock方法的调用 |
不能在 when() 中传 null 或非 mock 对象;使用 when(mock.method()) 格式 |
MissingMethodInvocation 异常 |
通常因 when() 前调用了真实方法导致;检查是否误用 doReturn().when() |
| Stubbing 未被调用 | 参数不匹配时触发默认返回值(null/0/false);使用 eq() 或 any() 精确匹配 |
| void 方法无法使用 when().thenThrow() | 应改用 doThrow(ex).when(mock).voidMethod() |
| spy 对象真实方法被意外调用 | when() 会先执行真实方法;一律使用 doReturn().when() 避免副作用 |
4.4 性能与稳定性优化
- 减少冗余 Stubbing:只桩化测试需验证的路径,保留默认行为可减少开销
- 使用 String 常量或 Matcher 复用:避免魔法值,提高可维护性
- 避免过度链式 thenReturn:连续调用过多时可用
thenAnswer替代 - 处理并发测试:Mockito 默认线程不安全,不要在多个线程中同时使用同一个 mock 实例
- 使用 stubbing 顺序与严格性:
Mockito.lenient()可抑制不必要的 stubbing 警告
// 启用 lenient 模式(适用于部分桩未被调用的情况)
Worker lenientMock = mock(Worker.class, withSettings().lenient());
when(lenientMock.getName()).thenReturn("test");
最佳实践:优先使用
doReturn().when() 风格,因为它统一了 void 与非 void 方法的桩化语法,且避免了 spy 对象的副作用。仅在需要链式返回值或静态分析场景时才使用 when().thenReturn()。