
Java单元测试里怎么断言抛出特定异常
JUnit 4 和 JUnit 5 的处理方式完全不同,直接混用会导致 ExpectedException 规则失效或 assertThrows 编译报错。
- JUnit 4 用
@Rule+ExpectedException,但必须在测试方法执行前声明,且不支持捕获异常后校验消息内容(除非手动 try-catch) - JUnit 5 推荐用
assertThrows,返回异常实例,可链式调用getMessage()或getCause()做进一步断言 - 别在非测试代码里写
assert语句来检查异常——JVM 默认禁用assert,且它只接受布尔表达式,不能捕获异常对象
示例(JUnit 5):
Exception e = assertThrows<IllegalArgumentException>(() -> parseValue("abc"));</code><br><pre class="brush:php;toolbar:false;">assertEquals("Invalid number format", e.getMessage());
手写工具方法封装异常断言是否必要
当项目长期停留在 JUnit 4、又需要频繁校验异常消息或 cause 链时,才值得封装;否则直接用 assertThrows 更轻量、更易维护。
- 自定义方法容易绕过 IDE 的语法提示,比如写成
expectException(IllegalArgException.class),实际类名拼错却无报错 - 若需统一日志或监控,建议用 JUnit 5 的
@ExtendWith扩展机制,而非静态工具方法 - Spring Boot Test 中的
@TestConfiguration不影响异常断言逻辑,但要注意@Transactional可能让某些运行时异常被框架吞掉,导致断言失败
IDE调试时怎么快速确认异常是否真被抛出
光看控制台堆栈不够——有些异常被 try-catch 吞了但没打日志,或者被 Spring 的 @ExceptionHandler 拦截转成 HTTP 200 响应。
立即学习“Java免费学习笔记(深入)”;
- 在 IDEA 中,在
throw语句行设断点,勾选 “Break on caught exceptions”(注意不是 “on all exceptions”) - 用
Thread.currentThread().getStackTrace()打印调用栈,比依赖日志更直接,尤其适合异步场景 - 避免在 Lambda 里 throw 异常后用
Optional.orElseThrow()二次包装——这会让原始异常的stackTrace丢失关键帧
Mockito模拟方法抛异常时的典型陷阱
when(...).thenThrow() 看似简单,但类型擦除和 checked exception 处理最容易翻车。
- 对 checked exception(如
IOException),必须确保被 mock 的方法签名里声明了该异常,否则编译不通过 - 用
thenThrow(new RuntimeException())没问题,但写成thenThrow(RuntimeException.class)会触发 Mockito 内部反射创建,可能抛MockitoException - 如果 mock 对象是泛型类型(如
Service<string></string>),thenThrow后再调用verify时,注意泛型实参是否影响方法匹配——IDE 可能误标为冗余 verify
真正难的不是怎么写断言,而是分清「这个异常该不该被捕获」「谁负责记录它」「下游服务看到的是原始异常还是包装后的」——这些决定往往不在测试代码里,而在架构约定中。










