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"));
}

执行流程解析

  1. employee 是一个 mock 对象。
  2. 调用 when(employee.greet()) 时,Mockito 会“记录”这次调用,但并不真正执行业务逻辑
  3. Mockito 判断到这是在配置行为,于是等待后续的 thenReturn() 等指令来定义该方法的返回值。
  4. 后续真实调用 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);
}

执行流程解析

  1. 先调用 doThrow(...),告诉 Mockito “我要抛异常”。
  2. 接着调用 .when(employee),指定目标 mock 对象。
  3. 最后调用 .work(DayOfWeek.SUNDAY),明确要对哪个方法调用进行打桩。

⚠️ 注意:这里的 when() 和前面的 when() 不是同一个方法!它是 Stubber 接口提供的,作用是切换上下文,指向具体的 mock 实例。

✅ 为什么还要保留 when().thenXxx()?

既然 doXxx().when() 更通用(能处理 void 和非 void),那为啥不弃用 when().thenXxx()?原因有二:

  1. 可读性更强
    when(...).thenReturn(...) 更符合“当发生某调用,则返回某值”的自然语言逻辑,代码更易读。

  2. 支持链式配置多个行为
    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 换成 givendo 换成 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


原始标题:Difference Between when() and doXxx() Methods in Mockito

« 上一篇: Java周报,350