Jest测试运行时安全扩展全局配置对象
Jest的global-setup与teardown运行于独立进程,直接赋值global属性无法持久化。正确做法是将全局状态封装为可变对象(如global.testConfig),通过引用修改实现跨进程数据共享,确保setup、测试与teardown访问同一状态仓库。
Jest 的 global-setup 和 global-teardown 机制,表面上看起来是“一前一后”的钩子,但实际使用中暗藏不少陷阱。许多开发者写过这样的代码:在 setup 里往 global 上挂了一个变量,测试中修改了它,然后到 teardown 里想获取最终值,结果发现是 undefined。这其实不是 Jest 的 bug,而是对进程隔离机制的理解偏差。

Jest 的 global-setup/teardown 仅共享初始化后的全局对象快照,测试中直接赋值 global.xxx 不会持久化到 teardown 阶段;正确做法是将全局状态封装为可变对象(如 global.testConfig),通过引用修改实现跨生命周期数据共享。
上述总结已经把核心问题阐述清楚。但在实际项目中,不少工程师仍习惯使用 global.someValue = 2 这种写法,结果在 teardown 里读不到值。原因在于:Jest 的 global-setup 和 global-teardown 各自运行在独立的 Node.js 进程中(除非使用 --runInBand 强制单进程),并且 Jest 对 global 对象做了隔离处理。也就是在测试文件里执行 global.sometwo = 2,这个赋值只会影响测试进程自己的 global 副本,而 teardown 进程看到的 global 还是最初的那个快照——因此 global.sometwo 自然是 undefined。
简单来说,global 在跨进程间是独立副本,并非共享引用。要让 setup 和 teardown 看到同一个“状态仓库”,需要改变思路。
正确做法:把状态放在一个可变对象里
关键点在于:不要直接向 global 添加顶层属性,而是创建一个可变对象(比如 global.testConfig),然后将这个对象挂到 global 上。这样,所有进程操作的都指向同一个对象引用,只要对象本身没有被替换,修改它的属性就能跨进程生效。
具体步骤分为四步:
第一步:定义配置对象
单独创建一个文件,例如 testConfig.js,里面导出一个普通对象:
// testConfig.js
module.exports = {
someone: 1,
};
第二步:在 global-setup 中挂载
注意,这里不是将 testConfig 的每个属性摊开赋值,而是把整个对象挂到 global.testConfig 上:
// global-setup.ts
const testConfig = require('./testConfig');
module.exports = async (config, projectConfig) => {
// 挂载为全局可变容器(必须是引用类型!)
global.testConfig = testConfig;
};
第三步:测试中通过该容器读写状态
在测试文件里,修改的是 global.testConfig 这个对象的属性,而不是重新赋值 global.testConfig 本身——因为后者会切断引用。下面是一个带异常捕获的示例,展示如何在 catch 块里修改状态:
// testfile.test.ts
describe('sometitle', () => {
test('testTitle', () => {
try {
console.log(global.testConfig.someone); // 输出: 1
expect(false).toBe(true);
} catch (e) {
// 修改的是 testConfig 对象的属性,而非 global 本身
global.testConfig.sometwo = 2;
throw e;
}
});
});
第四步:在 teardown 中读取变更后的状态
因为 global.testConfig 指向的是同一个对象引用,所以 teardown 进程里可以直接看到之前测试中修改的属性:
// global-teardown.ts
module.exports = async (globalConfig, projectConfig) => {
console.log(global.testConfig.someone); // 1
console.log(global.testConfig.sometwo); // 2 正确输出
};
需要注意的几个关键点
- 引用类型是前提:
global.testConfig必须是一个对象(或数组等引用类型),不能是原始值(如数字、字符串)。因为只有对象才能通过引用修改实现跨进程共享,原始值赋值相当于创建新副本。 - TypeScript 类型声明:如果使用 TypeScript,建议为 global 添加类型声明,避免编译报错:
declare global { var testConfig: { someone: number; sometwo: number }; } - 场景适用性:这个方案在单进程模式(
--runInBand)下自然没问题,在大多数并行场景下也能正常工作。但如果启用了--workerThreads且需要严格跨线程通信,建议改用process.send()或外部存储(如内存数据库)。 - 避免滥用:全局状态毕竟有副作用,不建议在测试中频繁修改。它更适合用于调试信息收集、统计计数、生命周期协调等确实需要跨进程共享数据的场景。
总结一句话:不要直接往 global 上扔临时字段,而是把它当作一个“命名空间容器”,在里面挂一个可变对象统一管理。这样,setup、测试、teardown 三方能真正看到同一个状态仓库,安全、可靠,也算 Jest 环境下的最佳实践了。
