
在 mockito 测试中,当模拟的异常被代码内部捕获(而非向上抛出)时,无法直接断言异常发生;但可通过验证目标方法是否被调用,间接确认异常确实被触发。
在 mockito 测试中,当模拟的异常被代码内部捕获(而非向上抛出)时,无法直接断言异常发生;但可通过验证目标方法是否被调用,间接确认异常确实被触发。
在单元测试中,我们常需验证异常行为是否按预期执行。然而,当被 doThrow() 模拟的异常在被测代码中被 try-catch 捕获且未重新抛出或产生可观测副作用时,assertThrows() 等断言将失效——因为从测试视角看,“异常未逃逸”,程序正常返回。此时,真正的验证目标并非“异常是否被抛出”,而是“异常触发路径是否被执行”。
最可靠、符合测试契约的方式是:验证被模拟抛出异常的方法是否被调用。这基于一个关键事实:doThrow(IOException.class).when(mock).method() 的行为是——只要 method() 被调用,就必然抛出 IOException(无论是否被捕获)。因此,成功验证 verify(mock).method() 即等价于确认该异常已被触发。
以下是一个典型示例:
// 被测类
public class ServiceOrchestrator {
private final OptionalService optionalService;
public ServiceOrchestrator(OptionalService optionalService) {
this.optionalService = optionalService;
}
public void doFullOperation() {
try {
optionalService.blink(); // 可能抛出 IOException
} catch (IOException e) {
// 静默处理:无日志、无重试、无状态变更
}
}
}
interface OptionalService {
void blink() throws IOException;
}
对应测试应聚焦于方法调用验证:
@ExtendWith(MockitoExtension.class)
class ServiceOrchestratorTest {
@Mock
OptionalService mockOptionalService;
@InjectMocks
ServiceOrchestrator orchestrator;
@Test
void doFullOperation_shouldTriggerBlinkEvenIfExceptionIsCaught() {
// Arrange: 模拟 blink() 抛出 IOException
doThrow(IOException.class).when(mockOptionalService).blink();
// Act: 执行被测方法(异常将被捕获)
orchestrator.doFullOperation();
// Assert: 验证 blink() 确实被调用 → 间接证明 IOException 已触发
verify(mockOptionalService).blink();
}
}
✅ 关键要点总结:
- ❌ 不要尝试“监听”或“拦截”已捕获的异常(Java 无运行时异常捕获钩子);
- ✅ 始终验证受控方法调用(
verify(mock).method()),这是 Mockito 提供的、语义清晰且稳定的断言方式; - ⚠️ 若
blink()在某些分支中根本未被调用,则verify()将失败——这恰能暴露逻辑缺陷(如条件判断绕过关键路径); - ? 若业务逻辑允许,建议改进被测代码:例如在
catch块中记录 warn 日志,或设置标志位,使异常影响可观察——但这属于代码可测性优化,非测试技巧替代方案。
通过将验证焦点从“异常对象”转向“方法行为”,我们以轻量、可靠、符合 Mockito 设计哲学的方式,解决了“静默捕获异常”的测试难题。










