如何在 NodeJS 中模拟流

How to mock streams in NodeJS

我正在尝试对我的一个 node-js 模块进行单元测试,该模块大量处理流。我正在尝试模拟一个流(我将写入),因为在我的模块中我有我想触发的“.on('data/end)”监听器。本质上我希望能够做这样的事情:

var mockedStream = new require('stream').readable();

mockedStream.on('data', function withData('data') {
  console.dir(data);
});

mockedStream.on('end', function() { 
  console.dir('goodbye');
});

mockedStream.push('hello world');
mockedStream.close();

这会执行,但在我执行推送后 'on' 事件永远不会被触发(并且 .close() 无效)。

我能找到的关于流的所有指南都使用 'fs' 或 'net' 库作为创建新流 (https://github.com/substack/stream-handbook) 的基础,或者他们用 sinon 模拟它但是嘲笑很快就变得很长。

是否有提供像这样的虚拟流的好方法?

我不应该使用 Push,而应该使用“.emit(, );”

我的模拟代码现在可以工作了,看起来像:

var mockedStream = new require('stream').Readable();
mockedStream._read = function(size) { /* do nothing */ };

myModule.functionIWantToTest(mockedStream); // has .on() listeners in it

mockedStream.emit('data', 'Hello data!');
mockedStream.emit('end');

采纳答案仅部分正确。如果您只需要触发事件,那么使用 .emit('data', datum) 是可以的,但是如果您需要将此模拟流通过管道传输到其他任何地方,它就无法工作。

模拟可读流非常简单,只需要可读库。

  let eventCount = 0;
  const mockEventStream = new Readable({
    objectMode: true,
    read: function (size) {
      if (eventCount < 10) {
        eventCount = eventCount + 1;
        return this.push({message: `event${eventCount}`})
      } else {
        return this.push(null);
      }
    }
  });

现在您可以在任何地方传输此流,'data' 和 'end' 将触发。

节点文档中的另一个示例: https://nodejs.org/api/stream.html#stream_an_example_counting_stream

有一个更简单的方法:stream.PassThrough

我刚刚发现 Node 很容易被忽略 stream.PassThrough class,我相信这正是您要找的。

来自Node docs

The stream.PassThrough class is a trivial implementation of a Transform stream that simply passes the input bytes across to the output. Its purpose is primarily for examples and testing...

问题代码,修改:

const { PassThrough } = require('stream');
const mockedStream = new PassThrough(); // <----

mockedStream.on('data', (d) => {
    console.dir(d);
});

mockedStream.on('end', function() {
    console.dir('goodbye');
});

mockedStream.emit('data', 'hello world');
mockedStream.end();   //   <-- end. not close.
mockedStream.destroy();

mockedStream.push() 也可以,但作为 Buffer 所以你可能想要这样做:console.dir(d.toString());

基于@flacnut 的回答,我(在 NodeJS 12+ 中)使用 Readable.from() 构建预加载数据(文件名列表)的流:

    const mockStream = require('stream').Readable.from([
       'file1.txt',
       'file2.txt',
       'file3.txt',
    ])

在我的例子中,我想模拟 fast-glob.stream:

返回的文件名流
    const glob = require('fast-glob')
    // inject the mock stream into glob module
    glob.stream = jest.fn().mockReturnValue(mockStream)

正在测试的函数中:

  const stream = glob.stream(globFilespec)
  for await (const filename of stream) {
    // filename = file1.txt, then file2.txt, then file3.txt
  }

很有魅力!