如何为express.static模拟http.ServerResponse和http.IncomingMessage

时间:2015-09-16 01:00:46

标签: javascript node.js unit-testing express mocking

我测试自己的路由处理程序没有问题,但在这种情况下我想测试express的静态处理程序。我无法为我的生活弄清楚为什么它会悬挂。显然,我缺少一些回调或者我需要发出一些事件。

我尽力做出最小的例子。

var events = require('events');
var express = require('express');
var stream = require('stream');
var util = require('util');

function MockResponse(callback) {
  stream.Writable.call(this);
  this.headers = {};
  this.statusCode = -1;
  this.body = undefined;

  this.setHeader = function(key, value) {
    this.headers[key] = value;
  }.bind(this);

  this.on('finish', function() {
    console.log("finished response");
    callback();
  });
};

util.inherits(MockResponse, stream.Writable);

MockResponse.prototype._write = function(chunk, encoding, done) {
  if (this.body === undefined) {
    this.body = "";
  }
  this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
  done();
};

function createRequest(req) {
  var emitter = new events.EventEmitter();
  req.on = emitter.on.bind(emitter);
  req.once = emitter.once.bind(emitter);
  req.addListener = emitter.addListener.bind(emitter);
  req.emit = emitter.emit.bind(emitter);
  return req;
};

describe('test', function() {

  var app;

  before(function() {
    app = express();
    app.use(express.static(__dirname));
  });

  it('gets test.js', function(done) {

    var req = createRequest({
        url: "http://foo.com/test.js",
        method: 'GET',
        headers: {
        },
    });
    var res = new MockResponse(responseDone);
    app(req, res);

    function responseDone() {
      console.log("done");
      done();
    }

  });

});

设置,

mkdir foo
cd foo
mkdir test
cat > test/test.js   # copy and paste code above
^D
npm install express
npm install mocha
node node_modules/mocha/bin/mocha --recursive

它只是超时。

我错过了什么?

我还尝试将请求设为可读流。没有变化

var events = require('events');
var express = require('express');
var stream = require('stream');
var util = require('util');

function MockResponse(callback) {
  stream.Writable.call(this);
  this.headers = {};
  this.statusCode = -1;
  this.body = undefined;

  this.setHeader = function(key, value) {
    this.headers[key] = value;
  }.bind(this);

  this.on('finish', function() {
    console.log("finished response");
    callback();
  });
};

util.inherits(MockResponse, stream.Writable);

MockResponse.prototype._write = function(chunk, encoding, done) {
  if (this.body === undefined) {
    this.body = "";
  }
  this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
  done();
};

function MockMessage(req) {
  stream.Readable.call(this);
  var self = this;
  Object.keys(req).forEach(function(key) {
    self[key] = req[key];
  });
}

util.inherits(MockMessage, stream.Readable);

MockMessage.prototype._read = function() {
  this.push(null);
};


describe('test', function() {

  var app;

  before(function() {
    app = express();
    app.use(express.static(__dirname));
  });

  it('gets test.js', function(done) {

    var req = new MockMessage({
        url: "http://foo.com/test.js",
        method: 'GET',
        headers: {
        },
    });
    var res = new MockResponse(responseDone);
    app(req, res);

    function responseDone() {
      console.log("done");
      done();
    }

  });

});

我一直在挖掘。查看静态服务器内部我看到它通过调用fs.createReadStream创建了一个可读流。它确实有效

var s = fs.createReadStream(filename);
s.pipe(res);

所以试着让自己工作得很好

  it('test stream', function(done) {
    var s = fs.createReadStream(__dirname + "/test.js");
    var res = new MockResponse(responseDone);
    s.pipe(res);

    function responseDone() {
      console.log("done");
      done();
    }    
  });

我想也许是表达等待输入流完成的东西,但似乎也不是这样。如果我使用响应消费模拟输入流,它可以正常工作

  it('test msg->res', function(done) {
    var req = new MockMessage({});
    var res = new MockResponse(responseDone);
    req.pipe(res);

    function responseDone() {
      console.log("done");
      done();
    }    
  });

任何有关我可能缺少的信息都会有所帮助

注意:虽然对第三方模拟库的建议表示赞赏,但我仍然真的希望了解自己缺少的东西。即使我最终切换到某个库,我仍然想知道为什么这不起作用。

2 个答案:

答案 0 :(得分:10)

我发现了两个阻止finish回调被执行的问题。

  1. serve-static使用send模块,该模块用于从路径创建文件读取流并将其传递给res对象。但该模块使用on-finished模块检查响应对象中finished属性是否设置为false,否则为destroys the file readstream。因此,文件流永远不会有机会发出数据事件。

  2. express initialization会覆盖响应对象原型。因此,http响应原型覆盖了end()方法等默认流方法:

    exports.init = function(app){
      return function expressInit(req, res, next){
        ...
        res.__proto__ = app.response;
        ..
      };
    };
    

    为了防止这种情况,我在静态中间件之前添加了另一个中间件,将其重置为MockResponse原型:

    app.use(function(req, res, next){
      res.__proto__ = MockResponse.prototype; //change it back to MockResponse prototype
      next();
    });
    
  3. 以下是为使其与MockResponse合作所做的更改:

    ...
    function MockResponse(callback) {
      ...
      this.finished = false; // so `on-finished` module doesn't emit finish event prematurely
    
      //required because of 'send' module
      this.getHeader = function(key) {
        return this.headers[key];
      }.bind(this);
      ...
    };
    
    ...
    describe('test', function() {
    
      var app;
    
      before(function() {
        app = express();
    
        //another middleware to reset the res object
        app.use(function(req, res, next){
          res.__proto__ = MockResponse.prototype;
          next();
        });
    
        app.use(express.static(__dirname));
      });
    
      ...
    
    });
    

    修改

    正如@gman指出的那样,可以使用直接属性而不是原型方法。在这种情况下,用于覆盖原型的额外中间件是不必要的:

    function MockResponse(callback) {
      ...
      this.finished = false; // so `on-finished` module doesn't emit finish event prematurely
    
      //required because of 'send' module
      this.getHeader = function(key) {
         return this.headers[key];
      }.bind(this);
    
      ...
    
      //using direct property for _write, write, end - since all these are changed when prototype is changed
      this._write = function(chunk, encoding, done) {
        if (this.body === undefined) {
          this.body = "";
        }
        this.body += chunk.toString(encoding !== 'buffer' ? encoding : undefined);
        done();
      };
    
      this.write = stream.Writable.prototype.write;
      this.end = stream.Writable.prototype.end;
    
    };
    

答案 1 :(得分:3)

看来我的答案并不完整。由于某种原因,应用程序仅在找不到文件时才起作用。首先要调试的是在shell(或cmd)中执行以下操作:

export DEBUG=express:router,send

然后运行测试,您将获得更多信息。

与此同时,我仍在调查此事,暂时忽略我的答案。

-----------忽略这个,直到我确认它确实有效-----------

似乎表达静态不支持你给它的绝对路径(__dirname)。

尝试:

app.use(express.static('.'));

它会起作用。请注意,您当前的mocha运动员目录是'test /'

我必须承认这是一个相当神秘的问题。我试着通过这样做来“填补”它:

app.use(express.static(__dirname + '/../test')

但它仍然没有用。即使指定完整路径也没有解决这个问题。奇怪。