1. 概述

在使用 Mockito 编写单元测试时,我们可能会遇到一个常见的异常:UnnecessaryStubbingException。这个异常通常在我们配置了未被使用的 stub(桩函数)时抛出,是 Mockito 严格桩函数(Strict Stubbing)机制的一部分。

本文将从以下几个方面展开:

  1. 严格桩函数的设计哲学与好处
  2. UnnecessaryStubbingException 的触发条件及示例
  3. 如何绕过严格桩函数检查(使用 lenient 模式)
  4. 面向有经验的开发者,避免啰嗦解释,重点放在使用技巧和注意事项上

2. 严格桩函数(Strict Stubbing)

Mockito 从 2.x 版本开始逐步引入“严格”机制,旨在帮助开发者写出更干净、可维护的测试代码。相比 1.x 中宽松的 mock 使用方式,2.x 及以上版本默认开启严格桩函数验证。

✅ 严格桩函数的核心目标包括:

  • 发现测试中未被使用的桩函数
  • 减少冗余的测试代码
  • 提高测试的可读性和可维护性
  • 增强调试效率,避免 copy-paste 错误

✅ 默认启用方式

如果你使用以下方式初始化 mock:

  • MockitoJUnitRunner
  • MockitoJUnit.rule()

那么严格桩函数是默认启用的。

✅ 手动启用方式(非 Runner 场景)

如果你没有使用上述方式初始化 mock,可以手动开启:

Mockito.mockitoSession()
  .initMocks(this)
  .strictness(Strictness.STRICT_STUBS)
  .startMocking();

⚠️ 注意:从 Mockito 3.0 开始,所有桩函数默认都是严格验证的。


3. UnnecessaryStubbingException 示例

✅ 什么是“不必要的桩函数”?

简单来说,就是你配置了一个方法调用的返回值,但测试中从未调用该方法

✅ 示例代码

@Test
public void givenUnusedStub_whenInvokingGetThenThrowUnnecessaryStubbingException() {
    when(mockList.add("one")).thenReturn(true); // 这个 stub 没有被调用
    when(mockList.get(anyInt())).thenReturn("hello");
    assertEquals("List should contain hello", "hello", mockList.get(1));
}

✅ 报错信息

org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at com.baeldung.mockito.misusing.MockitoUnecessaryStubUnitTest.givenUnusedStub_whenInvokingGetThenThrowUnnecessaryStubbingException(MockitoUnecessaryStubUnitTest.java:37)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.

✅ 为什么会报错?

第一行配置的 mockList.add("one") 没有在后续测试中被调用,Mockito 认为这是多余的配置,提示你删除。

✅ 为什么重要?

虽然这个例子很简单,但在实际项目中,当你 mock 一个复杂的对象树时,这种提示可以帮助你快速定位到无效的 stub,提升调试效率。


4. 绕过严格桩函数验证(Lenient Stubbing)

有时候我们确实需要保留某些未被调用的 stub,但又不希望触发 UnnecessaryStubbingException,这时候可以使用 lenient stubbing

✅ 示例代码

@Test
public void givenLenientStub_whenInvokingGetThenThrowUnnecessaryStubbingException() {
    lenient().when(mockList.add("one")).thenReturn(true);
    when(mockList.get(anyInt())).thenReturn("hello");
    assertEquals("List should contain hello", "hello", mockList.get(1));
}

✅ 关键点

  • 使用 Mockito.lenient() 开启 lenient 模式
  • 被标记为 lenient 的 stub 不会参与严格验证
  • 适用于一些特殊情况,比如 setup 中的通用 stub,不一定每次都会被调用

⚠️ 建议:除非确实需要,否则尽量不要滥用 lenient,保持测试代码的干净才是王道。


5. 小结

  • Mockito 从 2.x 开始默认启用严格桩函数机制,目的是提升测试代码质量
  • UnnecessaryStubbingException 是未使用 stub 的典型报错
  • 可以使用 lenient() 来绕过严格检查,但应谨慎使用
  • 保持测试代码简洁、无冗余,是写出高质量测试的前提

建议: 如果你正在使用 Mockito 2.x 或以上版本,建议始终保持 strict stubbing 模式,这有助于发现潜在的测试代码问题。


🔗 想了解更多关于 Mockito 的测试技巧?欢迎查看我们的 Mockito 教程系列

📦 本文完整示例代码可在 GitHub 上找到。


原始标题:Mockito Strict Stubbing and The UnnecessaryStubbingException