如何为 express.static 模拟 http.ServerResponse 和 http.IncomingMessage [英] How to mock http.ServerResponse and http.IncomingMessage for express.static

查看:30
本文介绍了如何为 express.static 模拟 http.ServerResponse 和 http.IncomingMessage的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在测试自己的路由处理程序时没有遇到任何问题,但在这种情况下,我想测试 express 的静态处理程序.我一辈子都搞不清楚它为什么挂了.很明显,我遗漏了一些回调或一些我需要发出的事件.

I've had no trouble testing my own route handlers but in this case I want to test express's static handler. I can't for the life of me figure out why it's hanging. Clearly there's some callback I'm missing or some event I need to emit.

我尽量做最小的例子.

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

它只是超时.

我错过了什么?

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

I also tried making the request a Readable stream. No change

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 创建了一个可读流.它确实有效

I've still been digging. Look inside static-server I see it creates a Readable stream by calling fs.createReadStream. It does effectively

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

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

So trying that myself works just fine

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

我想可能是关于快速等待输入流完成的事情,但似乎也不是.如果我使用带有响应的模拟输入流,它就可以正常工作

I thought maybe it's something about express waiting for the input stream to finish but that doesn't seem to be it either. If I consume the mock input stream with the response it works just fine

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

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

任何我可能遗漏的见解都会有所帮助

Any insight what I might be missing would be helpful

注意:虽然对 3rd 方模拟库的建议表示赞赏,但我仍然很想了解我自己缺少什么.即使我最终切换到某个图书馆,我仍然想知道为什么这不起作用.

Note: while suggestions for 3rd party mocking libraries are appreciated I'm still really looking to understand what I'm missing to do it myself. Even if I eventually switch to some library I still want to know why this isn't working.

推荐答案

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

I found two issues that prevent the finish callback from being executed.

  1. serve-static 使用 send 模块,该模块用于从路径创建文件读取流并将其通过管道传输到 res 对象.但是该模块使用 on-finished 模块检查响应对象中的 finished 属性是否设置为 false,否则 破坏文件读取流.所以文件流永远没有机会发出数据事件.

  1. serve-static uses send module which is used to create file readstream from the path and pipe it to res object. But that module uses on-finished module which checks if finished attribute is set to false in response object, otherwise it destroys the file readstream. So filestream never gets a chance to emit data event.

express 初始化会覆盖响应对象原型.所以像 end() 这样的默认流方法被 http 响应原型覆盖:

express initialization overwrites the response object prototype. So the default stream methods like end() method is overwritten by http response prototype:

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

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

To prevent this, I added another middleware right before static middleware to reset it back to MockResponse prototype:

app.use(function(req, res, next){
  res.__proto__ = MockResponse.prototype; //change it back to MockResponse prototype
  next();
});

以下是使其与 MockResponse 一起使用的更改:

Here are the changes made to make it work with 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 所指出的,可以使用直接属性而不是原型方法.在这种情况下,不需要额外的中间件来覆盖原型:

As @gman pointed out, it is possible to use direct property instead of prototype method. In that case the extra middleware to overwrite prototype isn't necessary:

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;

};

这篇关于如何为 express.static 模拟 http.ServerResponse 和 http.IncomingMessage的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆