【问题标题】:How to mock http.ServerResponse and http.IncomingMessage for express.static如何为 express.static 模拟 http.ServerResponse 和 http.IncomingMessage
【发布时间】:2015-12-12 10:02:24
【问题描述】:

我在测试自己的路由处理程序时没有遇到任何问题,但在这种情况下,我想测试 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();
    }    
  });

任何我可能缺少的见解都会有所帮助

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

【问题讨论】:

  • 我猜是 before 运行异步。也许尝试使用回调来等待它完成?
  • 这不是 before 运行异步。这部分完成得很好

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


【解决方案1】:

我发现了两个问题导致finish 回调无法执行。

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

  2. express initialization 覆盖响应对象原型。所以像end()这样的默认流方法被http响应原型覆盖:

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

以下是使其与 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;

};

【讨论】:

  • 谢谢。我仍然遇到了各种我不完全理解的问题,但至少我有一些工作要做。作为一个例子,我改变'write_'做一个直接属性,那么我不应该关心express的原型更改,但这会中断,因为在其他地方express期望该对象确实是从OutgoingMessage继承的ServerResponse。为什么当你把原型放回去但我没有得到时它会起作用
  • 是的,直接财产应该可以工作。流的其他方法怎么样,比如 end ?你让它成为直接财产吗?因为,当我测试时,我实际上在end 方法上遇到了错误,而不是在_write 上。
【解决方案2】:

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

export DEBUG=express:router,send

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

同时我还在研究这个问题,暂时忽略我下面的答案。

-----------在我确认它确实有效之前忽略它-----------

似乎 express static 不喜欢你给它的绝对路径(__dirname)。

试试:

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

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

我不得不承认这是个谜。我尝试通过这样做来“充实”它:

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

但它仍然没有工作。即使指定完整路径也无法解决此问题。奇怪。

【讨论】:

  • 我发现大多数模块都有这个调试选项。我建议您在 node_modules 目录中搜索 require('debug') 并打开所有调试选项。它可能会让您深入了解正在发生的事情。我确实注意到 express 提到它将 env 设置为开发,它可能与它有关。我使用的调试标志列表是:DEBUG=express:router,send,finalhandler,express:application,express:router:layer,mocha:runnable,express:router:route,mocha:runnable,mocha:runner,express:view最后一件事,您可以进入 node_modules 目录并在文件中添加调试消息
  • 另外,静态使用了一个名为 send 的模块。如果你仔细研究它,你会看到有一条评论说它有多混乱并且需要重构。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-10-04
  • 2015-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多