1. 概述
在使用 Mockito 编写单元测试时,我们可能会遇到一个常见的异常:UnnecessaryStubbingException。这个异常通常在我们配置了未被使用的 stub(桩函数)时抛出,是 Mockito 严格桩函数(Strict Stubbing)机制的一部分。
本文将从以下几个方面展开:
- 严格桩函数的设计哲学与好处
- UnnecessaryStubbingException 的触发条件及示例
- 如何绕过严格桩函数检查(使用 lenient 模式)
- 面向有经验的开发者,避免啰嗦解释,重点放在使用技巧和注意事项上
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 上找到。