第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() 则延迟执行,适用于所有情况,是更安全的通用写法。
Mockito Stubbing 决策流程图 开始 Stubbing 方法是否有返回值? when().thenReturn() doReturn().when() thenThrow(Ex.class) thenAnswer(invocation) thenCallRealMethod() doThrow().when() doAnswer().when() doCallRealMethod().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()。