NodeJS中Jest测试全局Map变量跨用例残留如何重置
问题场景
基于Node.js的TypeScript项目使用Jest编写单元测试时,待测试函数存在修改模块级全局变量的逻辑,出现测试隔离问题:类型为TypeScript Map的模块级变量impCache一旦被前置测试用例修改,值会持久残留,导致后续测试用例单独运行可正常通过、和其他用例批量执行时因读取到旧缓存值失败。
问题相关代码
file1.ts
let impCache: Map<number, string> // 原代码中sint为笔误 export function functionToTest(paramStr: string, paramNum: number){ doALotOfThings() ChecksAndReturnExceptionEct() if (!impCache){ AtSomePointCouldModify_impCache() } doMoreStuff() }
file1.test.ts
describe('All tests'){ describe('this test1 never change impCache', ()=>{ it('does its works', () => { assert('all_is_ok').tobe(string) }) }) describe('this test2 never change impCache', ()=>{ it('does its works', () => { assert('all_is_ok').tobe(string) }) }) describe('this test3 changes impCache', ()=>{ it('does its works', () => { assert('all_is_ok').tobe(string) }) }) describe('this test4 changes impCache', ()=>{ it('does its works', () => { assert('this always fail when run with all test but succeeds alone, because the cache was already set by the previous test ').tobe(string) }) }) }
可行解决方案
以下三种方案可根据项目实际情况选择:
方案1:测试侧手动重置模块缓存(无业务代码侵入)
Jest默认会缓存已加载的模块,所以同一测试文件内多个用例运行时会共享同一个模块作用域,模块级变量自然会残留。可以在每个用例执行前清空模块缓存,重新导入待测试模块,让每个用例拿到全新的模块实例。
修改测试文件代码:describe('All tests', () => { let functionToTest: typeof import('./file1')['functionToTest']; beforeEach(() => { // 清空已缓存的模块 jest.resetModules(); // 重新导入,获得全新的模块作用域,impCache回到初始未赋值状态 const file1Module = require('./file1'); functionToTest = file1Module.functionToTest; }); // 原有测试用例逻辑保持不变即可 describe('this test1 never change impCache', ()=>{ it('does its works', () => { assert('all_is_ok').tobe(string) }) }) describe('this test2 never change impCache', ()=>{ it('does its works', () => { assert('all_is_ok').tobe(string) }) }) describe('this test3 changes impCache', ()=>{ it('does its works', () => { assert('all_is_ok').tobe(string) }) }) describe('this test4 changes impCache', ()=>{ it('does its works', () => { assert('expected value').tobe(string) }) }) })如果项目使用ESM模块规范,把require替换为动态导入即可:
const file1Module = await import('./file1'),同时给beforeEach回调加上async关键字。方案2:业务侧暴露缓存重置方法(逻辑可控)
如果业务本身有手动管理缓存的需求,可以直接在模块内暴露一个重置缓存的方法,测试时主动调用清空状态。
修改file1.ts:let impCache: Map<number, string> // 新增缓存重置方法 export function resetImpCache() { // 完全重置为初始未定义状态 impCache = undefined as any; // 如果需要保留Map实例只清空内容,改用 impCache?.clear() } export function functionToTest(paramStr: string, paramNum: number){ doALotOfThings() ChecksAndReturnExceptionEct() if (!impCache){ AtSomePointCouldModify_impCache() } doMoreStuff() }之后在测试文件的
beforeEach钩子,或者每个会修改缓存的用例的afterEach钩子里调用resetImpCache()即可。提示:如果担心重置方法被生产环境误调用,可以通过
process.env.NODE_ENV === 'test'做环境判断,或者在打包配置中剔除测试专用方法。方案3:全局配置Jest自动重置模块(适合多文件同类问题场景)
如果项目中大量存在这类模块级变量,不需要每个测试文件单独写重置逻辑,可以直接修改Jest配置文件,开启全局模块重置:// jest.config.js module.exports = { // 其他原有配置 resetModules: true, }开启后Jest会在每个测试用例执行前自动重置模块注册表,所有模块都会被重新加载,天然隔离用例间的模块状态。
内容的提问来源于stack exchange,提问作者MasterOfTheHouse

