使用@ParameterizedTest时如何处理Mockito的UnnecessaryStubbingException
在迁移测试到新版本Mockito时,用@ParameterizedTest碰到了UnnecessaryStubbingException:部分测试参数会触发Mock服务的调用,另一部分则不会。留着Stub的话,没触发调用的参数会抛异常;删了Stub的话,触发调用的参数又会导致测试失败。已知两种解决思路:用Mockito.lenient()或者拆分测试用例,有没有更优的方案?
待测试方法示例
public boolean process(String flag) { if (Objects.equals(flag, "flag1")) { throw new IllegalArgumentException("Oh no, exception!"); } boolean result = someService.execute(flag); if (result) { throw new IllegalArgumentException("Oh no, exception!"); } return result; }
参数化测试代码示例
@ParameterizedTest @MethodSource("getFlags") void shouldTestIfFlagWorks(String someFlag) { // Given Mockito.doReturn(true).when(someService).execute(someFlag); // When Throwable thrown = Assertions.catchThrowable(() -> serviceUnderTest.process(someFlag)); // Then Assertions.assertThat(thrown).hasMessage("Oh no, exception!"); } private static Stream<Arguments> getFlags() { return Stream.of( Arguments.of("flag1"), Arguments.of("flag2") ); }
更优解决方案
1. 按参数逻辑动态创建Stub
针对不同参数的执行路径,在测试里判断参数类型,只在需要的时候创建Stub。这样既不会产生未被使用的Stub,也能保证需要Stub的场景正常运行。
修改后的测试代码:
@ParameterizedTest @MethodSource("getFlags") void shouldTestIfFlagWorks(String someFlag) { // Given if (!"flag1".equals(someFlag)) { Mockito.doReturn(true).when(someService).execute(someFlag); } // When Throwable thrown = Assertions.catchThrowable(() -> serviceUnderTest.process(someFlag)); // Then Assertions.assertThat(thrown).hasMessage("Oh no, exception!"); } private static Stream<Arguments> getFlags() { return Stream.of( Arguments.of("flag1"), Arguments.of("flag2") ); }
这种方式既能保持参数化测试的完整性,又精准控制Stub的创建,避免不必要的Stub触发异常,是最推荐的方案。
2. 使用@StubOnly注解(Mockito 4.6+)
如果你的Mockito版本是4.6及以上,可以给Mock对象加上@StubOnly注解,这样Mockito会忽略该Mock上未被调用的Stub,不会抛出UnnecessaryStubbingException。不过这是针对整个Mock对象的全局设置,要注意别因此掩盖了真正的不必要Stub问题。
示例:
@Mock @StubOnly private SomeService someService;
3. 测试类级别启用宽松模式
在测试类上添加@MockitoSettings(strictness = Strictness.LENIENT),让整个测试类的Mock都采用宽松模式,避免不必要的Stub异常。但这种方式会放宽所有Mock的校验,可能隐藏其他测试中的问题,需要谨慎使用。
对比已知方案
- 拆分测试用例:虽然逻辑清晰,但会增加测试方法数量,参数多的时候不够高效。
Mockito.lenient():可以针对单个Stub设置宽松,但Stub数量多的时候会显得繁琐。
上面的动态创建Stub方案更精准,既保持了参数化测试的简洁性,又严格控制Stub的使用,不会放宽全局校验,是更优的选择。
内容的提问来源于stack exchange,提问作者jure

