junit 5 通过 assertthrows 验证被测方法主动抛出的异常,或用 mockito 的 when().thenthrow() 和 dothrow().when() 模拟依赖异常以测试容错逻辑,核心是验证代码对可恢复异常(如 ioexception、业务异常)的响应是否正确,而非掩盖编程错误。

JUnit 5 本身不直接模拟异常,而是配合断言机制验证异常是否按预期发生;真正“触发”异常行为,通常靠被测代码主动抛出,或用 Mockito 模拟依赖在特定条件下抛出异常。关键在于:**验证容错 ≠ 制造异常,而是确认代码对异常的响应是否正确**。
用 assertThrows 验证被测方法主动抛出异常
这是最常用、最轻量的方式,适用于被测方法内部逻辑会直接 throw 异常的场景(如参数校验、除零、空指针等)。
- 调用 assertThrows(异常类型.class, 执行逻辑),它会执行 lambda 表达式,并捕获指定类型的异常
- 返回异常实例,可进一步断言异常消息、原因等细节
- 若未抛出预期异常,测试失败
示例:
@Test
void testDivideByZero() {
ArithmeticException exception = assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0));
assertEquals("/ by zero", exception.getMessage());
}
用 Mockito 让 mock 对象抛出异常,验证上层容错逻辑
当异常来自外部依赖(如数据库、HTTP 客户端、支付网关),而你想测试服务层如何处理这个异常时,就需用 Mockito 控制依赖行为。
- 对 @Mock 对象使用 when().thenThrow(),让其方法调用抛出指定异常
- 确保被测对象(@InjectMocks)调用了该 mock 方法
- 再用 assertThrows 或 assertTrue/assertEquals 验证上层返回值、状态变更或日志等容错结果
示例:
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testFindUserWhenDbFails() {
// 模拟数据库访问抛出异常
when(userRepository.findById(1L)).thenThrow(new RuntimeException("DB connection timeout"));
// 验证服务层返回默认用户,而非崩溃
User result = userService.findUserOrFallback(1L);
assertNotNull(result);
assertEquals("guest", result.getUsername());
}
验证 void 方法抛异常:用 doThrow().when()
对于返回 void 的方法(如 save()、send()),不能用 when().thenThrow()(因无返回值可“when”),必须用 doThrow().when() 语法。
- doThrow(RuntimeException.class).when(mockObj).methodName()
- 后续调用 mockObj.methodName() 就会立即抛出异常
- 常用于测试事务回滚、补偿逻辑、告警触发等场景
示例:
@Test
void testOrderSaveFailureTriggersCompensation() {
doThrow(new SQLException("Lock wait timeout")).when(orderRepository).save(any());
orderService.processOrder(new Order());
// 验证补偿动作是否执行
verify(compensationService).refund(any());
}
注意边界:别测“不该捕获的异常”
容错测试要聚焦业务定义的可恢复异常,不是所有异常都要兜底。
- 避免对 NullPointerException、IllegalArgumentException 等编程错误做“优雅处理”——它们应暴露问题,而非掩盖
- 优先验证 受检异常(IOException、SQLException) 和 业务自定义异常(InsufficientBalanceException)
- 检查 try-catch 是否覆盖了正确范围,是否误吞异常或漏写日志











