Jest运行Strapi单元测试卡住无法结束的解决方法
Jest运行Strapi单元测试卡在固定数量用例的排查方案
核心排查路径
1. 优先替换旧版文档推荐的require聚合测试写法
旧版Strapi文档给出的单入口require所有测试文件的写法,本质是把所有拆分测试文件的用例全部注入到同一个测试上下文、同一个Jest worker进程中运行,随着用例数增加,很容易触发单进程的句柄、内存阈值导致挂起。
直接删除聚合入口文件里的所有require()语句,改为在Jest配置文件中用原生规则匹配所有测试文件:
// jest.config.js module.exports = { // 匹配你存放测试文件的目录下所有.test.js结尾的文件 testMatch: ['**/tests/**/*.test.js'], testEnvironment: 'node', // 先把单用例超时调到30秒,排除默认5s超时无报错的情况 testTimeout: 30000, }
改完后Jest会自动给测试文件分配worker进程,隔离不同文件的测试上下文,80%的同类固定数量用例挂起问题都能通过这个调整解决。
2. 排查未正确释放的测试资源
如果改完加载模式还是卡住,逐一检查所有测试文件的生命周期逻辑:
- 所有在
beforeAll/beforeEach中初始化的Strapi实例、数据库连接、临时文件、mock服务、定时任务,必须在afterAll/afterEach中显式销毁,标准初始化模板如下:
let strapiInstance; beforeAll(async () => { strapiInstance = await require('@strapi/strapi')().load(); global.strapi = strapiInstance; }, 30000); afterAll(async () => { // 显式销毁实例,释放数据库连接、端口占用 await strapiInstance.destroy(); // 清理测试产生的临时文件、sqlite测试库 const fs = require('fs/promises'); await fs.rm('./.tmp/test.sqlite', { force: true }); // 如果业务代码里加了常驻定时任务,显式清理所有定时器 jest.clearAllTimers(); });
- 不要在多个测试文件中重复初始化Strapi实例不做销毁,通常累积到80个左右用例时,数据库连接池、系统文件句柄会被占满,后续用例拿不到资源就会一直处于待执行状态。这类底层依赖持有的句柄很多时候不会被
--detectOpenHandles参数捕获。
3. 定位具体卡住的单用例
给Jest启动命令加--verbose参数运行,观察控制台输出停在哪个用例,单独运行该用例所在的测试文件:
npx jest tests/your-stuck-test-file.test.js --verbose
重点检查该用例的三类问题:
- 异步逻辑没有正确返回Promise、没有调用
done()回调 - 发起了未被mock的外部HTTP请求、数据库长查询,一直等待响应没有触发超时
- 代码里写了常驻的
setInterval/长连接逻辑,没有被清理导致事件循环无法进入下一阶段
4. 兜底配置验证
如果暂时定位不到具体的句柄泄漏点,不要只在命令行传启动参数,直接把配置写到jest.config.js里(部分Jest版本对命令行传入的forceExit参数,在单文件require加载用例的场景下不生效):
module.exports = { // 其他原有配置 forceExit: true, detectOpenHandles: true, watchAll: false, maxWorkers: "50%", // 限制worker数量,避免占满系统资源 bail: 0, // 禁止单个用例卡住就终止整个队列 }
验证小技巧:可以在全局afterAll钩子中加一句
console.log(process._getActiveHandles().length)打印当前活跃句柄数,如果跑完所有用例后句柄数还大于2,说明确实存在未释放的资源。
内容的提问来源于stack exchange,提问作者Charlie Camus
相关产品推荐
相关产品推荐

