简体   繁体   English

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

[英]How to mock http.ServerResponse and http.IncomingMessage for express.static

I've had no trouble testing my own route handlers but in this case I want to test express's static handler. 我测试自己的路由处理程序没有问题,但在这种情况下我想测试express的静态处理程序。 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. 显然,我缺少一些回调或者我需要发出一些事件。

I tried to make the smallest example I could. 我尽力做出最小的例子。

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

  });

});

Setup, 设定,

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

it just times out. 它只是超时了。

What am I missing? 我错过了什么?

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

  });

});

I've still been digging. 我一直在挖掘。 Look inside static-server I see it creates a Readable stream by calling fs.createReadStream . 查看静态服务器内部我看到它通过调用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 任何有关我可能缺少的东西都会有所帮助

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. 即使我最终切换到某个库,我仍然想知道为什么这不起作用。

I found two issues that prevent the finish callback from being executed. 我发现了两个阻止执行finish回调的问题。

  1. serve-static uses send module which is used to create file readstream from the path and pipe it to res object. serve-static使用send模块,该模块用于从路径创建文件读取流并将其传递给res对象。 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 . 但该模块使用on-finished模块检查响应对象中是否将finished属性设置为false,否则会破坏文件读取流 So filestream never gets a chance to emit data event. 因此,文件流永远不会有机会发出数据事件。

  2. express initialization overwrites the response object prototype. 表达式初始化会覆盖响应对象原型。 So the default stream methods like end() method is overwritten by http response prototype: 因此,http响应原型会覆盖end()方法等默认流方法:

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

    To prevent this, I added another middleware right before static middleware to reset it back to MockResponse prototype: 为了防止这种情况,我在静态中间件之前添加了另一个中间件,将其重置为MockResponse原型:

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

Here are the changes made to make it work with MockResponse : 以下是使其与MockResponse一起使用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));
  });

  ...

});

EDIT: 编辑:

As @gman pointed out, it is possible to use direct property instead of prototype method. 正如@gman指出的那样,可以使用直接属性而不是原型方法。 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;

};

It appears my answer is not complete. 看来我的答案并不完整。 For some reason the app works only if the file is not found. 由于某种原因,应用程序仅在找不到文件时才起作用。 First thing to debug is do the following in your shell (or cmd): 首先要调试的是在shell(或cmd)中执行以下操作:

export DEBUG=express:router,send

then run the test, you'll get more info. 然后运行测试,你会得到更多的信息。

Meanwhile I am still looking into this, for now, ignore my answer below. 与此同时,我仍然在研究这个问题,现在,请忽略我的答案。

----------- ignore this till I verify that it does work ----------- -----------忽略这个,直到我确认它确实有效-----------

It seems like express static does not favor the absolute path you give it (__dirname). 似乎表达静态并不支持你给它的绝对路径(__dirname)。

Try: 尝试:

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

and it will work. 它会起作用。 Note that your current dir for the mocha runner is 'test/' 请注意,您当前的mocha跑步者的目录是'test /'

I have to admit this is quite a mistery. 我不得不承认这是一个相当神秘的问题。 I tried 'fulling' it by doing: 我通过这样做尝试'填补'它:

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

but still it didn't work. 但它仍然没有用。 Even specifying a full path did not solve this. 即使指定完整路径也没有解决这个问题。 Strange. 奇怪。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM