如何用Jest模拟fs.createReadStream并测试其事件回调?
测试fs.createReadStream的事件回调函数调用
你的核心需求是验证可读流触发data时functionTest被调用、触发end时functionTest2被调用,下面针对你纠结的mock-fs和memfs给出具体实现方案,同时补充更轻量化的直接模拟流的方式:
方案对比与实现
1. 使用mock-fs:无需修改原代码
mock-fs可以全局替换原生fs模块,直接在内存中创建虚拟文件,让原代码的fs.createReadStream读取这个虚拟文件,自然触发流事件。适合不想改动业务代码的场景。
示例代码(结合Jest):
const fs = require('fs'); const mockFs = require('mock-fs'); const { jest } = require('@jest/globals'); // 假设你的流处理逻辑封装在此函数中 function processFile(filePath) { const readStream = fs.createReadStream(filePath, { highWaterMark: 260 * 1000 }); readStream.on("data", () => functionTest()); readStream.on("end", () => functionTest2("example")); } test('验证流事件回调调用', (done) => { // Mock目标函数 const functionTest = jest.fn(); const functionTest2 = jest.fn(); global.functionTest = functionTest; global.functionTest2 = functionTest2; // 创建300KB的虚拟文件(大于260KB,确保触发2次data事件) mockFs({ '/test-file.txt': Buffer.alloc(300 * 1000, 'x'), }); // 执行流处理逻辑 processFile('/test-file.txt'); // 异步断言(流事件是异步触发) setTimeout(() => { expect(functionTest).toHaveBeenCalledTimes(2); expect(functionTest2).toHaveBeenCalledWith('example'); mockFs.restore(); // 恢复真实文件系统 done(); }, 50); });
2. 使用memfs:轻量内存文件系统
memfs是独立的内存文件系统,API和原生fs完全兼容,适合需要模拟完整文件系统操作、且不想全局替换fs的场景,通常需要通过依赖注入传递memfs的方法。
示例代码:
const { jest } = require('@jest/globals'); const { fs: memFs } = require('memfs'); // 调整处理函数,支持传入自定义的createReadStream方法 function processFile(createReadStream, filePath) { const readStream = createReadStream(filePath, { highWaterMark: 260 * 1000 }); readStream.on("data", () => functionTest()); readStream.on("end", () => functionTest2("example")); } test('验证流事件回调调用', (done) => { // Mock目标函数 const functionTest = jest.fn(); const functionTest2 = jest.fn(); global.functionTest = functionTest; global.functionTest2 = functionTest2; // 在memfs中创建虚拟文件 memFs.writeFileSync('/test-file.txt', Buffer.alloc(300 * 1000, 'x')); // 传入memfs的createReadStream执行处理逻辑 processFile(memFs.createReadStream, '/test-file.txt'); // 异步断言 setTimeout(() => { expect(functionTest).toHaveBeenCalledTimes(2); expect(functionTest2).toHaveBeenCalledWith('example'); done(); }, 50); });
3. 直接模拟可读流:无需第三方文件系统工具
如果只需要验证回调函数调用,完全可以跳过文件系统模拟,直接用Node.js的Readable类创建自定义流,手动触发data和end事件,更聚焦于流的事件逻辑。
示例代码:
const fs = require('fs'); const { jest } = require('@jest/globals'); const { Readable } = require('stream'); function processFile(filePath) { const readStream = fs.createReadStream(filePath, { highWaterMark: 260 * 1000 }); readStream.on("data", () => functionTest()); readStream.on("end", () => functionTest2("example")); } test('验证流事件回调调用', () => { // Mock目标函数 const functionTest = jest.fn(); const functionTest2 = jest.fn(); global.functionTest = functionTest; global.functionTest2 = functionTest2; // 创建自定义可读流,手动推送chunk并触发end const mockStream = new Readable({ read() { this.push(Buffer.alloc(260 * 1000, 'x')); // 第一个chunk this.push(Buffer.alloc(40 * 1000, 'x')); // 第二个chunk this.push(null); // 触发end事件 } }); // 替换fs.createReadStream返回自定义流 jest.spyOn(fs, 'createReadStream').mockReturnValue(mockStream); // 执行处理逻辑 processFile('/任意路径.txt'); // 同步断言(自定义流同步触发事件) expect(functionTest).toHaveBeenCalledTimes(2); expect(functionTest2).toHaveBeenCalledWith('example'); // 恢复mock fs.createReadStream.mockRestore(); });
方案选择建议
- 若原代码直接依赖原生
fs,不想做任何修改:选mock-fs - 若需要模拟完整文件系统操作,或希望避免全局替换
fs:选memfs - 若只需要验证回调函数调用,不想引入额外依赖:选直接模拟可读流的方式
内容的提问来源于stack exchange,提问作者emenikke26
相关产品推荐
相关产品推荐

