← 中文资源

简体中文版本

Jest 测试结束不退出 — 定位并关闭开放句柄

使用 detectOpenHandles 找出未关闭的服务器、定时器与数据库连接,在 afterAll 中释放资源,让 Jest 自然退出而不是依赖 forceExit。

阅读英文原文

现象

测试全部通过后出现 Jest did not exit one second after the test run has completed;CI 任务一直挂到超时被杀;只有加 --forceExit 才会退出;警告中列出 TCP server 或 timer 等开放句柄。

诊断

开启句柄检测并串行执行:

npx jest --detectOpenHandles --runInBand
npx jest --detectOpenHandles --runInBand 2>&1 | grep -B2 -A12 'Jest has detected the following' | head -80

报告会给出每个句柄的创建堆栈,通常指向缺少 afterAll 清理的模块。--runInBand 可以避免 worker 进程掩盖真实来源。若报告显示所有测试已通过但进程仍等待,即可确认是句柄泄漏;若测试仍未结束,则是测试本身超时。用 --forceExit 能立即退出,只能证明诊断,并不是修复。

修复

在 afterAll 或 afterEach 中关闭所有资源:等待 server.close() 回调、调用 pool.end() 或 client.disconnect()、清理 setInterval,并停止后台 worker。把客户端创建放到 beforeAll 而不是模块顶层,避免 import 时就建立连接;对默认会开连接的库使用 mock:

let server;
let pool;
beforeAll(async () => {
  pool = await createPool();
  server = app.listen(0);
});
afterAll(async () => {
  await new Promise((resolve) => server.close(resolve));
  await pool.end();
});

验证

去掉 --forceExit 运行完整测试集,进程应自行以退出码 0 结束;再跑一次 --detectOpenHandles,报告应为空。用 CI 相同的 worker 数量复跑,若只有多 worker 时才泄漏,说明某个每 worker 资源没有释放。不要长期保留 forceExit,它会截断异步清理并可能丢失覆盖率写入。