如何在NodeJS中模拟流?

27

我正在尝试对我一个处理流程较为复杂的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();

这段代码可以执行,但在进行 push 操作后 'on' 事件没有被触发(并且 .close() 是无效的)。

我所找到的有关流的指南都使用 'fs' 或 'net' 库作为创建新流的基础 (https://github.com/substack/stream-handbook),或者用 sinon 进行模拟,但是模拟变得非常冗长。

有没有一种简便的方法来提供这样的虚拟流?

6个回答

43

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

我刚刚发现了Node的非常容易被忽视的stream.PassThrough类,我认为这就是你要找的。

来自Node文档

stream.PassThrough类是一个转换流的微不足道的实现,它只是将输入字节传递到输出。 它的主要目的是用于示例和测试...

来自问题的代码,修改后:

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());


这个完美地解决了问题。在我看来,这应该是被采纳的答案。 - Tim

21

我本应该使用".emit(<event>, <data>)",而不是使用Push。

现在,我的模拟代码可以正常运行,并且看起来像这样:

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');

更正:new require('stream').Readable()(注意大写字母R) - Jonny
5
require 不是一个构造函数,不使用 new 关键字也能正常工作。 - Spets
1
@Spets 这里并不需要 constructor,Readable 就可以了。 - Rafael Vidaurre

12

接受的答案只是部分正确。如果你只需要触发事件,使用 .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”会触发。

来自Node文档的另一个示例: https://nodejs.org/api/stream.html#stream_an_example_counting_stream


7

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

    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
  }


运行良好!

0
这是一个简单的实现,它使用jest.fn(),目的是验证由fs.createWriteStream()创建的流中写入的内容。 jest.fn()的好处在于,尽管在此测试函数中,对fs.createWriteStream()stream.write()的调用是内联的,但这些函数不需要直接由测试调用。
    const fs = require('fs');
    const mockStream = {}
    
    test('mock fs.createWriteStream with mock implementation', async () => {
      const createMockWriteStream = (filename, args) => {
        return mockStream;
      }

      mockStream3.write = jest.fn();
      fs.createWriteStream = jest.fn(createMockWriteStream);
  
      const stream = fs.createWriteStream('foo.csv', {'flags': 'a'});
      await stream.write('foobar');
      expect(fs.createWriteStream).toHaveBeenCalledWith('foo.csv', {'flags': 'a'});
      expect(mockStream.write).toHaveBeenCalledWith('foobar');
  })


0

我必须将结果设置为缓冲区:

const str = '{ "fileName": "file.pdf" }';
const buf = new Buffer(str.length);

for (let i = 0; i < str.length ; i++) {
  buf[i] = str.charCodeAt(i);
}


let eventCount = 0;
const mockedStream = new Readable({
  objectMode: true,
  read: function () {
    if (eventCount < 1) {
      eventCount = eventCount + 1;
      return this.push(buf);
    } else {
      return this.push(null);
    }
  }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接