测试express中间件并捕获错误
我试图断言在express中间件中异步抛出的错误: 要测试的中间件:测试express中间件并捕获错误,express,mocha.js,Express,Mocha.js,我试图断言在express中间件中异步抛出的错误: 要测试的中间件: const request = require('request'); const middleware = function (options) { if (!options) { throw new Error('Options are missing.'); // gets catched } request(options.uri, (err, response) => { if(e
const request = require('request');
const middleware = function (options) {
if (!options) {
throw new Error('Options are missing.'); // gets catched
}
request(options.uri, (err, response) => {
if(err) {
throw err;
}
});
return function (req, res, next) {}
}
module.exports = middleware;
mocha
测试:
describe('middleware', () => {
describe('if async error is thrown', () => {
it('should return an error', done => {
try {
middleware({
uri: 'http://unkown'
});
} catch (err) {
assert.equal('Error: getaddrinfo ENOTFOUND unkown unkown:80', err.toString());
return done();
}
});
});
})
问题是,err
不会在测试中被捕获:
Uncaught Error: getaddrinfo ENOTFOUND unkown unkown:80
at errnoException (dns.js:27:10)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:78:26)
我知道这是因为错误是异步抛出的,但我不知道如何在这里解决它。当您使用异步代码时,
try catch
&throw
无法帮助您,因为它们只处理同步代码
解决方案
const request = require('request');
const middleware = function(options) { // <--- outer scope
var api_output = null; // Store the output of the HTTP request here
if (!options) throw new Error('Options are missing.'); // Express shouldn't handle this error, hence we throw it (also this error is synchronous)
return function(req, res, next) { // <--- inner scope
if (!api_output) { // Check if the output of the HTTP request has already been saved (so as to avoid calling it repeatedly)
request(options.uri, (error, response, body) => { // Perform the HTTP request
if (error) return next(error); // Pass the error to express error-handler
api_output = { // Save the output of the HTTP request in the outer scope
response: response,
body: body
};
req.api_output = api_output; // Set the output of the HTTP request in the req so that next middleware can access it
next(); // Call the next middleware
});
} else { // If the output of the HTTP request is already saved
req.api_output = api_output; // Set the output of the HTTP request in the req so that next middleware can access it
next(); // Call the next middleware
}
};
}
module.exports = middleware;
现在,关于测试 在您的mocha测试框架中,您可以使用
request
模块调用您的API,就像您在中间件函数中使用它调用外部API一样。然后您可以轻松地断言输出:
try catch
来处理来自中间件函数的错误
注意我使用术语“快速中间件”仅指像
函数(req,res,next){}
这样的中间件。我使用术语“中间件”来指代称为中间件的函数。基本问题是,您从异步代码中抛出。简而言之:不要那样做,那很糟糕;-)
所以,我想你有几个选择。哪一个最适合您取决于您的用例
场景1:异步设置功能
将同步设置函数转换为异步设置函数,即不要返回
中间件函数,而是使用回调将其交给调用者。这样,调用者的代码会变得更糟,但是您有一个干净的同步/异步代码分割,并且不要混合不适合混合的范例
const middleware = function (options, callback) {
if (!options) {
throw new Error('Options are missing.');
}
request(options.uri, (error, response, body) => {
if (error) {
return callback(error);
}
callback(null, function (req, res, next) {
// ...
next();
});
});
};
场景2:在每次调用中间件时执行请求
不要只调用一次request
,而是在中间件每次运行时都这样做。我不知道这是否有意义,但通过这种方式,您可以始终同步返回,只需处理中间件本身的异步
const middleware = function (options, callback) {
if (!options) {
throw new Error('Options are missing.');
}
return function (req, res, next) {
request(options.uri, (error, response, body) => {
if (error) {
return next(error);
}
// ...
next();
});
});
};
场景3:将请求外包
第三个也是最好的选择是将请求外包,并将结果交给中间件的设置功能,而不是让设置功能来执行请求
这不仅解决了同步与异步的问题,还使测试变得更容易,因为您不依赖HTTP调用,而是可以手动移交所需的结果。在中间件中,从try-catch中返回一个承诺。因此,您的catch
块将“拒绝(err)”
我会这样做:
const request = require('request');
const middleware = function (options) {
return new Promise((resolve,reject)=> {
if (!options) {
reject('Options are missing.');
}
request(options.uri, (err, response) => {
if(err) {
reject(err)
} else {
resolve(response)
}
});
});
}
然后,对于您的测试用例,执行以下操作:
describe('middleware', () => {
describe('if async error is thrown', () => {
it('should return an error', (done) => {
middleware({uri: 'http://unkown'}).then((res)=>{
done(res) //this will fail because you expect to never hit this block
},(err)=>{
assert.equal('Error: getaddrinfo ENOTFOUND unkown unkown:80', err.toString());
done()
});
});
});
})
在返回中间件函数之前,请求的目的是什么?从OpenId Connect配置中检索稍后在中间件中调用的jwks URI。为什么不使用承诺?本质上,在中间件函数中,从try-catch返回一个承诺。因此,您的catch块将拒绝(err)
@LostJon您介意提供一个工作示例吗?建议的代码对每个传入的HTTP请求执行HTTP请求,而原始代码仅在创建中间件时执行,这是另一回事。@robertklep说得很好。我会做些改变accordingly@robertklep我有个问题。如果HTTP请求只需要执行一次,那么为什么要将其与中间件关联呢?我可以在服务器启动时发出请求,并将其存储在DB或某个全局变量中。您必须向OP询问从中间件执行此操作的原理,但我同意您的看法,即在全局级别上执行此类操作更好。@BlazeSahlzen这确实是一个有效的问题。如果我将请求放在中间件之外,我不仅“拥有”了中间件的引导,而且还拥有了并非所有人都喜欢的应用程序本身。我编辑了您的答案,使其看起来像一个独立的答案。你最初的措辞让它看起来像是对评论的回复。如果它是对注释的回复,那么它也应该是一条注释,人们可以标记您的答案以删除。@Louis谢谢,注释格式在显示代码块时有点粗糙,所以我在回答中加入了它。我非常感谢您的编辑,我将在以后的回复中更加透彻。谢谢,我尝试过了,但这需要我首先调用中间件(选项)
,在解析中,我可以在express中注册内部返回函数(例如,对于express mw)。我不想以这种方式“拥有”mw注册过程。我想以这种方式注册mw:app.use(middleware(options))
;
describe('middleware', () => {
describe('if async error is thrown', () => {
it('should return an error', (done) => {
middleware({uri: 'http://unkown'}).then((res)=>{
done(res) //this will fail because you expect to never hit this block
},(err)=>{
assert.equal('Error: getaddrinfo ENOTFOUND unkown unkown:80', err.toString());
done()
});
});
});
})