如何为 express.static 模拟 http.ServerResponse 和 http.IncomingMessage
How to mock http.ServerResponse and http.IncomingMessage for express.static
我在测试自己的路由处理程序时没有遇到任何问题,但在这种情况下,我想测试 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();
}
});
我想也许是关于 express 等待输入流完成的事情,但似乎也不是。如果我使用带有响应的模拟输入流,它就可以正常工作
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 方模拟库的建议很受欢迎,但我仍然很想了解我自己缺少的东西。即使我最终切换到某个库,我仍然想知道为什么这不起作用。
看来我的回答不完整。出于某种原因,该应用程序仅在未找到该文件时才有效。调试的第一件事是在 shell(或 cmd)中执行以下操作:
export DEBUG=express:router,send
然后运行测试,你会得到更多信息。
同时我还在调查这个问题,现在请忽略我下面的回答。
------------忽略这个,直到我确认它确实有效------------
似乎 express static 不支持你给它的绝对路径 (__dirname)。
尝试:
app.use(express.static('.'));
它会起作用的。请注意,您当前的 mocha 运行ner 目录是 'test/'
不得不承认这是一个谜。我尝试 'fulling' 这样做:
app.use(express.static(__dirname + '/../test')
但还是不行。即使指定完整路径也不能解决这个问题。奇怪。
我发现了两个阻止 finish
回调执行的问题。
serve-static
使用 send
模块,该模块用于从路径创建文件读取流并将其通过管道传输到 res
对象。但是该模块使用 on-finished
模块检查 finished
属性是否在响应对象中设置为 false,否则它 destroys the file readstream。所以文件流永远不会有机会发出数据事件。
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;
};
我在测试自己的路由处理程序时没有遇到任何问题,但在这种情况下,我想测试 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();
}
});
我想也许是关于 express 等待输入流完成的事情,但似乎也不是。如果我使用带有响应的模拟输入流,它就可以正常工作
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 方模拟库的建议很受欢迎,但我仍然很想了解我自己缺少的东西。即使我最终切换到某个库,我仍然想知道为什么这不起作用。
看来我的回答不完整。出于某种原因,该应用程序仅在未找到该文件时才有效。调试的第一件事是在 shell(或 cmd)中执行以下操作:
export DEBUG=express:router,send
然后运行测试,你会得到更多信息。
同时我还在调查这个问题,现在请忽略我下面的回答。
------------忽略这个,直到我确认它确实有效------------
似乎 express static 不支持你给它的绝对路径 (__dirname)。
尝试:
app.use(express.static('.'));
它会起作用的。请注意,您当前的 mocha 运行ner 目录是 'test/'
不得不承认这是一个谜。我尝试 'fulling' 这样做:
app.use(express.static(__dirname + '/../test')
但还是不行。即使指定完整路径也不能解决这个问题。奇怪。
我发现了两个阻止 finish
回调执行的问题。
serve-static
使用send
模块,该模块用于从路径创建文件读取流并将其通过管道传输到res
对象。但是该模块使用on-finished
模块检查finished
属性是否在响应对象中设置为 false,否则它 destroys the file readstream。所以文件流永远不会有机会发出数据事件。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;
};