1. 概述
Mockito 是 Java 生态中最主流的 mocking 框架之一。它能轻松实现创建 mock 对象、配置行为、捕获方法参数以及验证调用等功能。
本文聚焦于 行为配置(stubbing) 的两种方式:when().thenXxx()
和 doXxx().when()
。这两种语法都能完成任务,但设计初衷和适用场景略有不同。搞不清它们的区别,容易在写单元测试时踩坑,尤其是处理 void
方法时莫名其妙编译失败。
我们来深入剖析为什么 Mockito 要提供两套看似重复的 API。
2. when() 方法:适用于有返回值的方法
先看一个简单的 Employee
接口:
interface Employee {
String greet();
void work(DayOfWeek day);
}
假设我们要为 greet()
方法打桩(stub),让它返回 "Hello"
。这是标准操作:
@Test
void givenNonVoidMethod_callingWhen_shouldConfigureBehavior() {
// given
when(employee.greet()).thenReturn("Hello");
// when
String greeting = employee.greet();
// then
assertThat(greeting, is("Hello"));
}
✅ 执行流程解析:
employee
是一个 mock 对象。- 调用
when(employee.greet())
时,Mockito 会“记录”这次调用,但并不真正执行业务逻辑。 - Mockito 判断到这是在配置行为,于是等待后续的
thenReturn()
等指令来定义该方法的返回值。 - 后续真实调用
employee.greet()
时,就返回预设值。
⚠️ 关键点:when()
的参数必须是一个有返回值的方法调用,因为 when()
本身需要接收这个返回值作为入参。
那么问题来了:如果方法是 void
呢?比如我们想让 work(DayOfWeek.SUNDAY)
抛出异常:
@Test
void givenVoidMethod_callingWhen_wontCompile() {
// given
when(employee.work(DayOfWeek.SUNDAY)).thenThrow(new IAmOnHolidayException());
// when
Executable workCall = () -> employee.work(DayOfWeek.SUNDAY);
// then
assertThrows(IAmOnHolidayException.class, workCall);
}
❌ 结果:编译失败!
原因很简单:work()
是 void
方法,没有返回值,不能作为 when()
的参数。Java 语法不允许你把一个 void
调用嵌套在另一个方法调用里。
3. doXxx() 方法:专为 void 方法设计
为了解决 void
方法无法被 when()
包裹的问题,Mockito 提供了 doXxx()
系列方法,比如 doThrow()
、doReturn()
、doAnswer()
等。
重写上面的测试:
@Test
void givenVoidMethod_callingDoThrow_shouldConfigureBehavior() {
// given
doThrow(new IAmOnHolidayException()).when(employee).work(DayOfWeek.SUNDAY);
// when
Executable workCall = () -> employee.work(DayOfWeek.SUNDAY);
// then
assertThrows(IAmOnHolidayException.class, workCall);
}
✅ 执行流程解析:
- 先调用
doThrow(...)
,告诉 Mockito “我要抛异常”。 - 接着调用
.when(employee)
,指定目标 mock 对象。 - 最后调用
.work(DayOfWeek.SUNDAY)
,明确要对哪个方法调用进行打桩。
⚠️ 注意:这里的 when()
和前面的 when()
不是同一个方法!它是 Stubber
接口提供的,作用是切换上下文,指向具体的 mock 实例。
✅ 为什么还要保留 when().thenXxx()?
既然 doXxx().when()
更通用(能处理 void
和非 void
),那为啥不弃用 when().thenXxx()
?原因有二:
可读性更强
when(...).thenReturn(...)
更符合“当发生某调用,则返回某值”的自然语言逻辑,代码更易读。支持链式配置多个行为
when()
返回的是OngoingStubbing<T>
,其thenXxx()
方法也返回自身,支持连续配置:when(employee.greet()) .thenReturn("Hello") .thenReturn("Hi") .thenThrow(new RuntimeException());
而
doXxx()
返回的是Stubber
,.when(mock)
后返回的是 mock 对象本身(如Employee
),不再支持链式打桩。
⚠️ doXxx() 的一个“坑”
虽然 doXxx().when()
可以用于非 void
方法,但不推荐。因为如果方法本身抛出异常,doXxx()
会忽略异常并继续执行,而 when().thenXxx()
会正确处理。
例如:
// ❌ 危险:即使 greet() 抛异常,也会被忽略
doReturn("Hello").when(employee).greet();
// ✅ 安全:能正确捕获并处理异常
when(employee.greet()).thenReturn("Hello");
4. BDDMockito:更贴近行为驱动开发
如果你的项目使用 BDD(行为驱动开发),Mockito 还提供了语义更清晰的 BDDMockito
类,把 when
换成 given
,do
换成 will
:
@Test
void givenNonVoidMethod_callingGiven_shouldConfigureBehavior() {
// given
given(employee.greet()).willReturn("Hello");
// when
String greeting = employee.greet();
// then
assertThat(greeting, is("Hello"));
}
@Test
void givenVoidMethod_callingWillThrow_shouldConfigureBehavior() {
// given
willThrow(new IAmOnHolidayException()).given(employee).work(DayOfWeek.SUNDAY);
// when
Executable workCall = () -> employee.work(DayOfWeek.SUNDAY);
// then
assertThrows(IAmOnHolidayException.class, workCall);
}
这种风格更贴近 Given-When-Then 的测试结构,团队协作时语义更统一。
5. 总结
场景 | 推荐语法 | 原因 |
---|---|---|
✅ 有返回值的方法 | when().thenReturn() |
可读性好,支持链式配置 |
✅ void 方法 |
doXxx().when() |
唯一选择,编译通过 |
✅ BDD 风格项目 | given().willReturn() / willXxx().given() |
语义清晰,结构统一 |
简单粗暴记法:
- 普通方法用
when().thenXxx()
void
方法用doXxx().when()
- BDD 项目统一用
given
/will
示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/testing-modules/mockito