junit验证异常的核心是声明式断言操作抛出指定类型异常:junit 4用@test(expected=...)或expectedexception rule,junit 5推荐assertthrows获取异常实例进行完整断言,还可使用assertdoesnotthrow确认不抛异常。

在 JUnit 中验证方法是否按预期抛出异常,关键不是“拦截”异常(Java 中无法真正拦截已抛出的异常),而是**声明式地断言某个操作会抛出指定类型的异常**。不同版本的 JUnit 提供了不同写法,核心逻辑一致:执行被测代码,检查是否抛出了期望的异常类型(以及可选的消息或原因)。
JUnit 4:使用 @Test(expected = ...)
最简单直接的方式,适用于只需校验异常类型、不关心消息内容的场景:
@Test(expected = IllegalArgumentException.class)
public void shouldThrowIllegalArgumentExceptionWhenNullInput() {
myService.process(null); // 这行必须抛出 IllegalArgumentException,否则测试失败
}
⚠️ 注意:该方式无法验证异常消息、无法在异常抛出后做额外断言(比如检查状态是否回滚),且一旦方法中提前抛出其他异常,测试也会通过(只要类型匹配),灵活性低。
JUnit 4/5:使用 ExpectedException Rule(JUnit 4)或 assertThrows(JUnit 5 推荐)
更精准、更主流的做法是显式调用并捕获异常,再对异常对象做完整断言。
JUnit 4 + ExpectedException Rule(需引入):
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldThrowWithSpecificMessage() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("input must not be null");
myService.process(null);
}
JUnit 5(推荐)——使用 assertThrows:
@Test
public void shouldThrowIllegalArgumentExceptionAndCheckMessage() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
myService.process(null);
});
assertEquals("input must not be null", exception.getMessage());
// 还可以继续断言 cause、stack trace 等
}
✅ 优势明显:可获取异常实例,自由断言类型、消息、嵌套原因、甚至自定义字段;语义清晰;支持 lambda 表达式,代码简洁。
JUnit 5 高级用法:结合 Assertions.assertDoesNotThrow
当需要**确认某段代码不抛出任何异常**(即“静默成功”)时,用:
@Test
public void shouldNotThrowWhenValidInput() {
assertDoesNotThrow(() -> myService.process("valid"));
}
这比空的 try-catch + fail() 更语义化,也避免了遗漏异常处理的隐患。
常见误区提醒
- 不要在测试方法里自己 try-catch 异常后只写
fail()—— 这样丢失了异常上下文,且写法冗长易错; - 避免用
@Test(expected = ...)校验复杂业务异常(如带 errorCode 字段的自定义异常),因为它无法访问异常实例; - 若被测方法可能抛出多个异常类型,应针对每种情况单独写测试用例,而不是在一个测试里“覆盖所有”;
- 确保异常是在 lambda 或被测调用中真实发生的,不要把构造异常对象的代码放在 assertThrows 里(例如
assertThrows(..., () -> new IllegalArgumentException())是错误的)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











