如何在不重构的情况下对含硬编码依赖的原生JS进行单元测试?
遗留原生JS应用的Jest单元测试问题
我有一个遗留原生JS应用,代码示例如下:
var thing = new Thing(); function do_thing() { // Logic to test here } // export module
该文件包含许多需单元测试的重要计算逻辑,但我不想对整个应用重构。
我尝试用Jest测试,代码如下:
/** * @jest-environment jsdom */ const mod = require('thing.js'); test('Test', () => { mod.do_thing(); });
但运行报错:
ReferenceError: Thing is not defined
即使移除var thing,若do_thing调用其他文件的函数(遗留项目常见情况),仍会报错:
ReferenceError: foo() is not defined
请问有何解决建议?能否用jest.fn或jest.mock等方法,无需大量重构即可模拟依赖?
解决建议
不用大规模重构,完全可以通过Jest的全局模拟能力处理这些依赖问题,具体方案如下:
1. 模拟全局Thing构造函数
遗留代码直接使用了全局的Thing,可以在测试文件里提前把它挂载到window(jsdom环境的全局对象)上,用jest.fn()模拟:
/** * @jest-environment jsdom */ // 模拟全局的Thing构造函数,可按需定义实例属性/方法 window.Thing = jest.fn().mockImplementation(() => { return { /* 比如: getValue: jest.fn().mockReturnValue(100) */ }; }); const mod = require('./thing.js'); test('Test do_thing', () => { mod.do_thing(); // 还能验证Thing是否被实例化 expect(window.Thing).toHaveBeenCalled(); });
2. 模拟其他全局函数(如foo())
如果do_thing调用了其他文件的全局函数foo,同样挂载到全局并模拟:
/** * @jest-environment jsdom */ window.Thing = jest.fn(); // 模拟全局foo函数,自定义返回值或逻辑 window.foo = jest.fn().mockReturnValue('模拟计算结果'); const mod = require('./thing.js'); test('Test do_thing uses foo', () => { mod.do_thing(); expect(window.foo).toHaveBeenCalled(); });
3. 清理全局模拟,避免测试污染
如果担心全局模拟影响其他测试,用Jest的beforeEach和afterEach做前后置处理:
/** * @jest-environment jsdom */ let originalThing; let originalFoo; beforeEach(() => { // 保存原全局对象 originalThing = window.Thing; originalFoo = window.foo; // 挂载模拟 window.Thing = jest.fn(); window.foo = jest.fn(); }); afterEach(() => { // 恢复原全局对象 window.Thing = originalThing; window.foo = originalFoo; }); const mod = require('./thing.js'); test('Test do_thing', () => { mod.do_thing(); expect(window.Thing).toHaveBeenCalled(); });
4. 针对简单导出的模块模拟
如果遗留文件最后有基础导出(比如module.exports = { do_thing }),也可以用jest.mock间接模拟依赖,但全局模拟更适配完全无模块化的原生JS代码。
内容的提问来源于stack exchange,提问作者Olle Härstedt
相关产品推荐
相关产品推荐

