并行运行Jest测试时出现TypeError: Converting circular structure to JSON
Jest并行测试报错:循环引用无法序列化JSON(串行测试正常)
问题现象
并行运行Jest测试时触发以下错误,测试套件直接失败:
Jest worker encountered 4 child process exceptions, exceeding retry limit at ChildProcessWorker.initialize (node_modules/jest-worker/build/workers/ChildProcessWorker.js:181:21) node:internal/child_process/serialization:159 const string = JSONStringify(message) + '\n'; ^ TypeError: Converting circular structure to JSON --> starting at object with constructor 'Socket' | property '_httpMessage' -> object with constructor 'ClientRequest' --- property 'socket' closes the circle at stringify (<anonymous>) at writeChannelMessage (node:internal/child_process/serialization:159:20) at process.target._send (node:internal/child_process:845:17) at process.target.send (node:internal/child_process:745:19) at reportSuccess (C:\Users\User1\Documents\App1\App2\api-gateway\node_modules\jest-worker\build\workers\processChild.js:82:11)
但单独执行每个测试,或使用--runInBand参数串行运行时,所有测试均可正常通过。
原因分析
Jest并行模式下会启动多个子进程执行测试,子进程与主进程之间通过JSON序列化传递测试结果、上下文数据。如果测试代码或依赖模块中存在循环引用的对象(比如报错中的Socket与ClientRequest互相引用),子进程在序列化这些对象时就会触发TypeError。
串行模式下所有测试在同一个进程内执行,不需要跨进程传递数据,因此不会触发该问题。
解决方案
1. 清理测试上下文的循环引用对象
- 检查
beforeAll/afterAll/beforeEach/afterEach中的资源初始化逻辑,确保测试结束后彻底销毁HTTP客户端、Socket连接等带循环引用的实例:let httpClient; beforeEach(() => { httpClient = axios.create({ baseURL: 'http://localhost:3000' }); }); afterEach(async () => { // 销毁客户端,清除循环引用 if (httpClient?.destroy) { await httpClient.destroy(); } }); - 避免在测试中把带循环引用的对象挂载到全局作用域或模块级变量中。
2. 隔离模块作用域
- 在测试文件开头调用
jest.resetModules(),确保每个测试用例重新导入模块,避免模块级变量在子进程间共享:beforeEach(() => { jest.resetModules(); }); test('example test', () => { const moduleWithSideEffect = require('../module-with-side-effect'); // 执行测试逻辑 }); - 或使用
jest.isolateModules包裹模块导入逻辑,完全隔离模块作用域:test('isolated test', () => { jest.isolateModules(() => { const isolatedModule = require('../module-with-side-effect'); // 执行测试逻辑 }); });
3. 临时禁用并行模式(应急方案)
如果暂时无法定位循环引用来源,可以在Jest配置中强制串行执行:
- 在
jest.config.js中添加:module.exports = { maxWorkers: 1, }; - 或运行测试时添加参数:
jest --runInBand
4. 检查模块级副作用
避免在模块顶部直接创建HTTP连接、Socket实例等带循环引用的对象,这类模块级副作用会导致每个子进程初始化时都生成残留对象。将初始化逻辑移到测试的生命周期钩子中。
内容的提问来源于stack exchange,提问作者Andrew Kerious
相关产品推荐
相关产品推荐

