Jest运行测试时是否重定义全局Error变量导致实例断言失败?
问题描述
编写的数据库连接前置测试用例如下:
beforeAll(async () => { try { await db.connect(); } catch(e: any) { expect(e).toBeInstanceOf(Error); } });
测试运行失败,抛出反常错误:断言两侧打印的构造函数均为Error,但实例类型判断未通过:
expect(received).toBeInstanceOf(expected) Expected constructor: Error Received constructor: Error
项目相关配置如下:
jest.config.js
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: "ts-jest", testEnvironment: "node", testPathIgnorePatterns: ["/node_modules/"] };
package.json 相关片段
{ "devDependencies": { "@types/jest": "28.1.3", "@types/node": "18.0.0", "jest": "28.1.1", "ts-jest": "28.0.5", "ts-node": "10.8.1", "typescript": "4.7.4", }, "scripts": { "coverage": "jest --coverage", "test": "jest", } }
tsconfig.json
{ "compilerOptions": { "alwaysStrict": true, "esModuleInterop": true, "moduleResolution": "Node", "noImplicitAny": true, "noImplicitThis": true, "noImplicitReturns": true, "strict": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strictNullChecks": true, "strictPropertyInitialization": true, "target": "ESNext" }, "exclude": ["*.d.ts"], "include": ["*.ts", "test/*.ts"] }
问题根因
这是Jest 28+配合ts-jest、tsconfig设置target: "ESNext"时的典型跨执行上下文(realm)问题:
- Jest运行测试时,会通过Node的
vm模块创建独立的沙箱执行环境,沙箱内的全局对象和Node原生全局对象完全隔离,沙箱内的Error构造函数和Node原生的Error构造函数是两个内存地址独立的不同函数,仅名称相同。 - 当
tsconfig.target设为ESNext时,ts-jest编译代码不会对内置原生类型做兼容处理,如果引入的db模块是在沙箱外(比如通过ts-node直接挂载到Node原生上下文)加载的,它抛出的错误是原生上下文的Error实例,用沙箱内的Error做instanceof判断自然会返回false,打印构造函数名时两边都显示为Error,就会出现题述的反常报错。
解决方案
按优先级推荐以下解决方式:
- 调整TypeScript编译目标:将tsconfig中的
target从ESNext改为稳定的ES版本(如ES2021),让ts-jest编译时自动处理内置类型的跨上下文兼容问题:{ "compilerOptions": { "target": "ES2021" } } - 调整ts-jest配置:在jest.config.js中关闭ts-jest的模块隔离模式,避免编译时生成独立的全局构造函数引用:
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: "ts-jest", testEnvironment: "node", testPathIgnorePatterns: ["/node_modules/"], globals: { 'ts-jest': { isolatedModules: false } } }; - 绕开instanceof判断:如果不想调整配置,可以修改断言逻辑,通过构造函数名、错误对象特征判断类型,避免跨上下文的instanceof失效问题:
beforeAll(async () => { try { await db.connect(); } catch(e: any) { expect(e.constructor.name).toBe('Error'); // 可增加错误特征判断提升准确性 expect(typeof e.message).toBe('string'); } }); - 全局Error对齐(不推荐):新增setup文件在测试启动前将沙箱内的Error替换为Node原生Error,该方法可能影响其他依赖沙箱隔离的测试逻辑:
首先新建jest.setup.js:
再在jest.config.js中引入该文件:global.Error = Error;module.exports = { // 其余原有配置 setupFiles: ['./jest.setup.js'] }
内容的提问来源于stack exchange,提问作者Daniele Ricci
相关产品推荐
相关产品推荐

