在NodeJS中使用流进行TDD /测试

时间:2014-04-17 18:55:02

标签: node.js testing stream tdd

我一直试图找到一种合理的方法来测试使用流的代码。有没有人找到合理的方法/框架来帮助测试在nodejs中使用流的代码?

例如:

var fs = require('fs'),
    request = require('request');

module.exports = function (url, path, callback) {
  request(url)
    .pipe(fs.createWriteStream(path))
    .on('finish', function () {
      callback();
    });
};

我目前测试这种类型代码的方法或者包括使用流来简化代码,以至于我可以将它抽象为未经测试的代码块,或者通过编写类似的东西:

var rewire = require('rewire'),
    download = rewire('../lib/download'),
    stream = require('stream'),
    util = require('util');

describe('download', function () {
  it('should download a url', function (done) {
    var fakeRequest, fakeFs, FakeStream;

    FakeStream = function () {
      stream.Writable.call(this);
    };

    util.inherits(FakeStream, stream.Writable);

    FakeStream.prototype._write = function (data, encoding, cb) {
      expect(data.toString()).toEqual("hello world")
      cb();
    };

    fakeRequest = function (url) {
      var output = new stream.Readable();

      output.push("hello world");
      output.push(null);

      expect(url).toEqual('http://hello');

      return output;
    };

    fakeFs = {
      createWriteStream: function (path) {
        expect(path).toEqual('hello.txt');
        return new FakeStream();
      }
    };

    download.__set__('fs', fakeFs);
    download.__set__('request', fakeRequest);

    download('http://hello', 'hello.txt', function () {
      done();
    });

  });
});

有没有人想出更优雅的测试流方式?

7 个答案:

答案 0 :(得分:11)

为此目的做了流测试。它不仅使流测试更清晰,而且还允许测试V1和V2流https://www.npmjs.com/package/streamtest

答案 1 :(得分:6)

我也一直在使用memorystream,但后来将我的断言置于finish事件中。这样看起来更像是对正在测试的流的真实使用:

require('chai').should();

var fs = require('fs');
var path = require('path');

var MemoryStream = require('memorystream');
var memStream = MemoryStream.createWriteStream();

/**
 * This is the Transform that we want to test:
 */

var Parser = require('../lib/parser');
var parser = new Parser();

describe('Parser', function(){
  it('something', function(done){
    fs.createReadStream(path.join(__dirname, 'something.txt'))
      .pipe(parser)
      .pipe(memStream)
      .on('finish', function() {

        /**
         * Check that our parser has created the right output:
         */

        memStream
          .toString()
          .should.eql('something');
        done();
      });
  });
});

检查对象可以这样做:

var memStream = MemoryStream.createWriteStream(null, {objectMode: true});
.
.
.
      .on('finish', function() {
        memStream
          .queue[0]
          .should.eql({ some: 'thing' });
        done();
      });
.
.
.

答案 2 :(得分:3)

我觉得你很痛苦。

我不知道任何框架来帮助测试流,但如果看看here, 我正在开发一个流库,你可以看到我如何解决这个问题。

这是我正在做的事情的想法。

var chai = require("chai")
, sinon = require("sinon")
, chai.use(require("sinon-chai"))
, expect = chai.expect
, through2 = require('through2')
;

chai.config.showDiff = false

function spy (stream) {
  var agent, fn
  ;
  if (spy.free.length === 0) {
    agent = sinon.spy();
  } else {
    agent = spy.free.pop();
    agent.reset();
  }
  spy.used.push(agent);
  fn = stream._transform;
  stream.spy = agent;
  stream._transform =  function(c) {
    agent(c);
    return fn.apply(this, arguments);
  };
  stream._transform = transform;
  return agent;
};

spy.free = [];
spy.used = [];


describe('basic through2 stream', function(){

  beforeEach(function(){
    this.streamA = through2()
    this.StreamB = through2.obj()
    // other kind of streams...

    spy(this.streamA)
    spy(this.StreamB)

  })

  afterEach(function(){
    spy.used.map(function(agent){
      spy.free.push(spy.used.pop())
    })
  })

  it("must call transform with the data", function(){
    var ctx = this
    , dataA = new Buffer('some data')
    , dataB = 'some data'
    ;

    this.streamA.pipe(through2(function(chunk, enc, next){
      expect(ctx.streamA.spy).to.have.been.calledOnce.and.calledWidth(dataA)
    }))

    this.streamB.pipe(through2(function(chunk, enc, next){
      expect(ctx.streamB.spy).to.have.been.calledOnce.and.calledWidth(dataB)
    }))

    this.streamA.write(dataA)
    this.streamB.write(dataB)

  })

})

请注意,我的间谍功能会封装_transform方法并调用我的间谍并调用原始_transform

此外,afterEach功能正在回收间谍,因为你最终可以创建数百个间谍。

当您想要测试异步代码时问题变得困难。然后答应你最好的朋友。我上面给出的链接有一些样本。

答案 3 :(得分:3)

将Stream读入内存并将其与预期的Buffer进行比较。

it('should output a valid Stream', (done) => {
  const stream = getStreamToTest();
  const expectedBuffer = Buffer.from(...);
  let bytes = new Buffer('');

  stream.on('data', (chunk) => {
    bytes = Buffer.concat([bytes, chunk]);
  });

  stream.on('end', () => {
    try {
      expect(bytes).to.deep.equal(expectedBuffer);
      done();
    } catch (err) {
      done(err);
    }
  });
});

答案 4 :(得分:2)

我还没有使用它,它已经很老了,但是https://github.com/dominictarr/stream-spec可能有所帮助。

答案 5 :(得分:2)

您可以使用间谍使用MemoryStreamsinon来测试流。以下是我测试一些代码的方法。

describe('some spec', function() {
    it('some test', function(done) {
        var outputStream = new MemoryStream();

        var spyCB = sinon.spy();

        outputStream.on('data', spyCB);

        doSomething(param, param2, outputStream, function() {
            sinon.assert.calledWith(spyCB, 'blah');

            done();
        });
    });
});

答案 6 :(得分:2)

我发现的最好方法是使用事件

const byline = require('byline');
const fs = require('fs');

it('should process all lines in file', function(done){
   //arrange
   let lines = 0;
   //file with 1000 lines
   let reader = fs.readFileStream('./input.txt');
   let writer = fs.writeFileStream('./output.txt');
   //act
   reader.pipe(byline).pipe(writer);
   byline.on('line', function() {
     lines++;
   });
   //assert
   writer.on('close', function() {
     expect(lines).to.equal(1000);
     done();
   });
});

通过传递完成作为回调,mocha一直等到它被调用后再继续。