
当异常在被测代码中被捕获且未向外传播时,无法直接断言其抛出行为;但可通过验证模拟方法是否被调用,间接确认异常确实被触发。
当异常在被测代码中被捕获且未向外传播时,无法直接断言其抛出行为;但可通过验证模拟方法是否被调用,间接确认异常确实被触发。
在使用 Mockito 进行单元测试时,doThrow() 可以轻松模拟异常,而 assertThrows() 适用于验证未被捕获的异常。但若异常在被测方法内部被 try-catch 捕获(例如空 catch 块),它不会传播到测试层,因此 assertThrows() 失效,也无法通过异常类型进行断言。
此时,核心思路应转向行为验证(behavior verification):既然异常由某个依赖方法(如 optionalService.blink())抛出,而该方法已被 Mockito 模拟,那么只要能证明该模拟方法确实在执行路径中被调用,即可合理推断异常已被触发——因为 doThrow(IOException.class).when(mock).method() 的语义保证:一旦该方法被调用,就一定会抛出指定异常(无论是否被捕获)。
✅ 正确做法是结合 verify() 断言方法调用:
doThrow(IOException.class).when(mockOptionalService).blink(); classUnderTest.doFullOperation(); // 验证 blink() 方法确实被执行了一次 verify(mockOptionalService).blink();
该断言成功,即意味着:
-
blink()被调用; - 根据 stub 规则,
IOException必然在此刻抛出; - 即使被
catch吞没,也不影响“抛出发生”这一事实。
⚠️ 注意事项:
-
verify()默认校验调用至少一次(times(1)),若业务逻辑中存在条件分支导致该方法可能不执行,需确保测试路径覆盖正确分支; - 不要误用
verifyNoInteractions()或never()—— 它们用于验证“未调用”,与本场景目标相反; - 空
catch块(如// No operation needed)通常是代码坏味道,建议在生产代码中记录日志或转为更明确的错误处理策略,但测试层面仍可安全验证其前置触发行为; - 若需进一步确认异常处理逻辑(如 fallback 行为),应在
catch块中引入可观测副作用(如设置状态标志、调用日志器、更新字段等),再通过状态断言验证。
总结:异常是否被抛出,取决于方法是否被调用;而是否被捕获,属于控制流问题,不影响“抛出事件”的发生。因此,验证模拟方法的调用,是最直接、可靠且符合 Mockito 设计哲学的解决方案。










