严格Stubbing(Strict Stubbing)是Mockito提供的一种增强测试严谨性的模式,旨在检测以下问题:
- 多余的Stubbing:配置了未被调用的方法桩。
- 不必要的Stubbing:Stubbing未被使用且不影响测试结果。
- 桩顺序错误:Stubbing被覆盖或错误使用。
通过严格模式,可以避免测试代码中的“虚假通过”(False Positive),确保每个Stubbing都有明确的目的。
1. 启用严格Stubbing
Mockito提供了两种方式启用严格模式:
1.1 全局启用(推荐)
在测试类上添加 @MockitoSettings(strictness = Strictness.STRICT_STUBS)
:
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.STRICT_STUBS) // 全局启用严格模式
public class OrderServiceTest {
// 测试代码...
}
1.2 手动启用
通过 MockitoSession
在单个测试中启用:
@Test
void testWithManualStrictness() {
try (MockitoSession session = Mockito.mockitoSession()
.strictness(Strictness.STRICT_STUBS)
.startMocking()) {
// 测试代码...
}
}
2. 严格模式下的错误场景与示例
2.1 多余的Stubbing
问题:配置了未被调用的Stubbing。
示例:
@Mock
private UserDao mockUserDao;
@Test
void findUser_ShouldReturnUser() {
// 多余的Stubbing:findByEmail未被调用
when(mockUserDao.findByUsername("alice")).thenReturn(new User());
when(mockUserDao.findById(1)).thenReturn(new User()); // 实际被调用的Stubbing
User user = userService.findUserById(1); // 调用findById(1)
verify(mockUserDao).findById(1);
}
错误信息:
Unnecessary stubbings detected.
2.2 不必要的Stubbing
问题:Stubbing存在但未被使用,且不影响测试结果。
示例:
@Test
void createUser_ShouldNotDependOnFindMethod() {
when(mockUserDao.findByUsername("alice")).thenReturn(null); // 未被使用
doNothing().when(mockUserDao).save(any());
userService.createUser("alice", "pass123");
verify(mockUserDao).save(any());
}
错误信息:同上,提示存在不必要的Stubbing。
3. 解决方案
3.1 删除多余的Stubbing
直接移除未被调用的桩代码:
@Test
void findUser_ShouldReturnUser() {
// 删除多余的findByUsername桩
when(mockUserDao.findById(1)).thenReturn(new User());
User user = userService.findUserById(1);
verify(mockUserDao).findById(1);
}
3.2 显式标记宽松Stubbing
使用 lenient()
忽略特定Stubbing的检测:
@Test
void createUser_ShouldAllowUnusedStubbing() {
// 标记为宽松Stubbing
lenient().when(mockUserDao.findByUsername("alice")).thenReturn(null);
doNothing().when(mockUserDao).save(any());
userService.createUser("alice", "pass123");
verify(mockUserDao).save(any());
}
4. 严格模式的进阶用法
4.1 验证Stubbing顺序
严格模式会检测Stubbing的覆盖顺序。例如,重复定义同一方法的Stubbing可能导致意外覆盖:
@Test
void testStubbingOrder() {
when(mockList.get(0)).thenReturn("A");
when(mockList.get(0)).thenReturn("B"); // 覆盖前一个Stubbing
assertEquals("B", mockList.get(0)); // 通过
}
严格模式下会提示 PotentialStubbingProblem
,建议使用 doReturn()
链式配置:
when(mockList.get(0))
.thenReturn("A")
.thenReturn("B");
4.2 检测无效的Stubbing参数
严格模式会检查参数匹配器的使用是否正确:
@Test
void testInvalidMatcher() {
// 错误:混合精确值和匹配器
when(mockUserDao.find(eq("alice"), anyString())).thenReturn(null);
}
错误信息:
Invalid use of argument matchers!
5. 最佳实践
- 仅Mock必要的交互:避免过度Mock,仅覆盖影响当前测试的依赖方法。
- 及时清理无用桩代码:定期运行测试并修复严格模式提示的问题。
- 合理使用
lenient()
:仅在明确需要时放宽检测,如共享测试配置中的通用桩。 - 结合日志分析:通过
Mockito.mockingDetails(mock).printInvocations()
调试Stubbing调用。
总结
严格Stubbing模式通过强制检测冗余和无效的桩代码,显著提升单元测试的健壮性和可维护性。结合示例中的修复策略,开发者可以快速定位问题并优化测试逻辑,确保每个Stubbing都精准服务于测试目标。