单元测试中模拟try-with-resources的关闭异常,需用Mockito mock AutoCloseable并doThrow预设close()抛异常,或自定义FailingResource类控制关闭行为,验证资源关闭及抑制异常是否正确添加。

在单元测试中模拟 try-with-resources 的资源关闭异常,关键在于控制资源的 close() 方法行为。由于 try-with-resources 会在 finally 块中自动调用 close(),而该调用本身可能抛出异常(尤其是当 try 块已抛异常时,关闭异常会作为抑制异常被添加),因此测试重点是验证:资源是否被正确关闭、关闭异常是否被正确处理或抑制。
使用 Mockito 模拟可关闭资源并触发 close 异常
Mockito 3.4.0+ 支持对 AutoCloseable(或 Closeable)接口的 mock,并可指定 close() 抛出异常:
- 创建 mock 资源对象,实现
AutoCloseable - 用
doThrow()预设close()调用行为 - 确保资源在 try-with-resources 中被使用,触发自动关闭逻辑
示例:
@Test
void testTryWithResources_handlesCloseException() {
// 模拟资源,close 时抛出 IOException
AutoCloseable resource = mock(AutoCloseable.class);
doThrow(new IOException("close failed")).when(resource).close();
// 执行 try-with-resources(内部逻辑应包含 resource 使用)
assertThrows(IOException.class, () -> {
try (resource) {
// 模拟业务操作,例如读取数据
throw new IOException("main operation failed");
}
});
// 验证 close 被调用一次
verify(resource).close();
}
注意:上述测试中,try 块先抛出异常,随后 close 再抛异常,后者会被抑制(suppressed)。若想捕获并断言抑制异常,需检查主异常的 getSuppressed() 数组。
立即学习“Java免费学习笔记(深入)”;
验证抑制异常是否正确添加
Java 规范要求:当 try 块和 finally(即 close)都抛异常时,close 异常作为 suppressed exception 附加到主异常上。测试中可显式检查:
- 捕获主异常
- 断言其
getSuppressed().length == 1 - 断言 suppressed 异常类型和消息符合预期
示例:
@Test
void testCloseExceptionIsSuppressed() {
AutoCloseable resource = mock(AutoCloseable.class);
doThrow(new SQLException("DB close error")).when(resource).close();
Exception thrown = assertThrows(SQLException.class, () -> {
try (resource) {
throw new SQLException("query failed");
}
});
assertArrayEquals(
new String[]{"DB close error"},
Arrays.stream(thrown.getSuppressed())
.map(Throwable::getMessage)
.toArray(String[]::new)
);
}
使用自定义资源类(更可控,适合复杂场景)
当需要精确控制 close 行为(如首次调用不抛异常、第二次才抛),或资源有状态时,推荐手写一个测试专用的 AutoCloseable 实现:
- 定义一个内部类,实现
AutoCloseable,维护 close 计数器或开关标志 - 在
close()中按需抛异常(例如if (shouldFailOnClose) throw ...) - 便于在多个测试用例中复用和重置状态
示例:
static class FailingResource implements AutoCloseable {
private final boolean failOnClose;
private boolean closed;
FailingResource(boolean failOnClose) {
this.failOnClose = failOnClose;
}
@Override
public void close() throws IOException {
closed = true;
if (failOnClose) {
throw new IOException("forced close failure");
}
}
}
@Test
void testWithCustomFailingResource() {
var resource = new FailingResource(true);
IOException thrown = assertThrows(IOException.class, () -> {
try (resource) {
throw new IOException("main work failed");
}
});
assertThat(thrown.getSuppressed()).hasSize(1);
}
避免常见陷阱
- 不要 mock 具体类(如 FileInputStream)的 close 方法:它们通常被标记为 final 或由 JVM 特殊处理,mockito 无法拦截;应 mock 接口或使用包装类
-
注意异常类型匹配:资源的
close()声明抛什么异常(如IOException),测试中抛出的异常必须兼容,否则编译不通过或行为不符合预期 - 不建议在测试中依赖真实资源(如文件、Socket)来触发 close 异常:环境不可控、速度慢、易受干扰;应以 mock 或轻量封装为主


















