
Mockito 的 spy() 默认调用真实方法,若需对某些方法进行 stub(打桩)而不执行其实际逻辑,必须使用 doReturn().when(spy).method() 语法,而非 when(spy.method()).thenReturn(),否则真实方法会在 stub 时被意外执行。
mockito 的 `spy()` 默认调用真实方法,若需对某些方法进行 stub(打桩)而不执行其实际逻辑,必须使用 `doreturn().when(spy).method()` 语法,而非 `when(spy.method()).thenreturn()`,否则真实方法会在 stub 时被意外执行。
在使用 Mockito 进行部分模拟(partial mocking)时,spy() 是一个关键工具:它基于真实对象创建代理,默认所有方法都调用原始实现,仅当显式 stub 时才覆盖行为。但一个常见误区是误用 when(...).thenReturn(...) 对 spy 对象的方法进行 stub —— 这会导致该方法在 stub 过程中被立即执行一次真实逻辑,不仅违背预期(如引发副作用、数据库调用或空指针异常),还可能使测试失败或结果不可控。
✅ 正确做法:对 spy 使用 doReturn() / doThrow() / doAnswer() 等 void-returning stubbing 语法:
@Test
public void myService_method_1_spy() {
// ✅ 正确:避免触发 serviceMethod_1 的真实逻辑
doReturn("call_serviceMethod_1_MOCK")
.when(myServiceSpy).serviceMethod_1();
// 其余方法保持真实行为(无需额外声明)
List<string> actualList = myServiceSpy.performService();
List<string> expectedList = Arrays.asList(
"call_serviceMethod_1_MOCK",
"call_serviceMethod_2_REAL",
"call_serviceMethod_3_REAL"
);
assertEquals(expectedList, actualList);
verify(myServiceSpy, times(1)).serviceMethod_1();
}</string></string>
⚠️ 错误写法(导致真实方法被执行):
// ❌ 危险!spy.serviceMethod_1() 会在 this 行被调用一次
when(myServiceSpy.serviceMethod_1()).thenReturn("mocked");
这是因为 when() 内部会先触发目标方法调用以捕获其调用链(尤其对 void 方法不适用),而 spy 的方法默认走真实实现 —— 因此 serviceMethod_1() 在 stub 阶段就被执行,打印日志、修改状态甚至抛出异常。
? 补充说明:
- doReturn(...).when(spy).method() 是专为 spy(及 mock)设计的“延迟 stub”语法,完全绕过实际方法执行;
- 对于 void 方法,只能使用 doNothing().when(spy).voidMethod() 或 doThrow(...),when(...) 语法不适用;
- 若需验证方法是否被调用(如 verify()),仍可正常使用,与 stub 方式无关;
- 注意:@Spy 字段注入需配合 @RunWith(MockitoJUnitRunner.class) 或 MockitoAnnotations.openMocks(this) 初始化,否则 spy 为 null。
? 最佳实践建议:
- 优先考虑重构:过度依赖 spy 往往暗示类职责过重或难以测试,可引入接口抽象或依赖注入提升可测性;
- 避免 spy 复杂对象(如 Spring Bean),因其可能含生命周期逻辑;必要时改用 @Mock + 手动注入依赖;
- 在 @Before 中完成 spy 初始化和基础 stub,保持测试用例专注业务逻辑验证。
通过正确选用 doReturn() 语法,你就能精准控制哪些方法走真实逻辑、哪些被拦截替换,真正发挥 spy() “部分模拟”的核心价值。











