You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:52:43