如何使用Jest对Node.js入口点文件进行单元测试
Node.js入口文件Jest单元测试实现方案
问题根源
你当前的入口脚本将业务逻辑写在顶层作用域,只要被require导入就会立即执行,因此测试时还未进入test代码块,逻辑就已经运行完成。
第一步:改造入口文件,拆分逻辑
将核心业务逻辑封装为可导出的独立函数,仅当文件被直接执行(而非被其他文件导入)时才自动调用逻辑,既不影响原有npm start的使用逻辑,也能支持测试时主动调用。
改造后的入口文件代码:
const fs = require("fs"); const summarizeData = require("./summarize-data"); // 封装核心逻辑为可调用函数 function entryPoint() { try { const fileName = process.argv[2]; if (!fileName) { throw Error("Please enter a file name. ex: npm start <filename>"); } if (!fs.existsSync(`${fileName}.json`)) { throw Error(`The file ${fileName}.json could not be found.`); } const jsonParsed = JSON.parse(fs.readFileSync(`${fileName}.json`, "utf8")); const data = summarizeData(jsonParsed); console.log(data); } catch (error) { throw Error(error); } } // 仅当文件被直接执行时才触发逻辑,被导入时不会自动运行 if (require.main === module) { entryPoint(); } module.exports = entryPoint;
第二步:编写Jest单元测试用例
测试时导入封装好的entryPoint函数,mock掉fs、process.argv、summarizeData等依赖,即可覆盖所有业务分支:
const fs = require('fs'); const summarizeData = require('./summarize-data'); const entryPoint = require('./entry-point'); // mock依赖模块 jest.mock('fs'); jest.mock('./summarize-data'); // 备份原始process.argv,避免测试污染环境 const originalArgv = process.argv; // mock console.log避免测试输出干扰 console.log = jest.fn(); afterEach(() => { // 每次测试后重置mock和参数,隔离用例 jest.clearAllMocks(); process.argv = originalArgv; }); test('未传入文件名时抛出对应错误', () => { process.argv = ['node', 'entry-point.js']; expect(() => entryPoint()).toThrowError('Please enter a file name. ex: npm start <filename>'); }); test('指定的json文件不存在时抛出对应错误', () => { process.argv = ['node', 'entry-point.js', 'test']; fs.existsSync.mockReturnValue(false); expect(() => entryPoint()).toThrowError('The file test.json could not be found.'); }); test('正常流程下读取文件、处理数据并输出结果', () => { const mockFileContent = JSON.stringify({ test: 'data' }); const mockSummary = '测试汇总结果'; process.argv = ['node', 'entry-point.js', 'test']; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(mockFileContent); summarizeData.mockReturnValue(mockSummary); entryPoint(); expect(fs.readFileSync).toHaveBeenCalledWith('test.json', 'utf8'); expect(summarizeData).toHaveBeenCalledWith(JSON.parse(mockFileContent)); expect(console.log).toHaveBeenCalledWith(mockSummary); });
可选方案:不改造入口文件做集成测试
如果你不想修改原有入口文件,可以通过child_process模块执行入口脚本,判断输出结果实现集成测试,但该方案无法做细粒度的依赖mock,执行速度也更慢,仅适合验证全流程结果。
内容的提问来源于stack exchange,提问作者Dan Zuzevich
相关产品推荐
相关产品推荐

