测试表达中间件并捕获错误

时间:2017-02-17 22:39:15

标签: express mocha

我试图断言在快速中间件中抛出异步的错误:

要测试的中间件:

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)

我知道发生这种情况是因为错误是异步的,但我不知道如何在这里解决它。

3 个答案:

答案 0 :(得分:1)

基本问题是您在异步代码中throw。简而言之:不要这样做,这很糟糕; - )

所以,恕我直言,你有几个选择。哪一个最适合您取决于您​​的使用案例。

场景1:异步设置功能

将同步变为异步设置功能,即不要return中间件功能,而是使用回调将其交给呼叫者。这样调用者的代码变得更糟,但是你有一个干净的同步/异步代码拆分,并且不要混合那些不适合混合的范例。

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调用,而是可以手动移交所需的结果。

答案 1 :(得分:0)

当您使用异步代码时,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;

我所做的只是返回一个快速中间件,只有在尚未调用的情况下调用外部API 。如果没有错误,那么它会将外部API的响应存储在api_output中,并将其传递给下一个可以使用它的快速中间件。

要了解其工作原理,了解JavaScript如何处理范围(look up closure)至关重要。

每次执行快速中间件时,我都没有调用第三方API,而是在第一次调用它时将该API的输出存储在快速中间件函数的外部范围内 。这样做的好处是第三方API的输出对外部和外部私有。内部范围,无法从应用程序中的任何其他位置访问。

如果有任何错误,则将其传递给next回调(这会触发快速错误处理函数)。请参阅"Express error handling",了解其工作原理。特别是这个 -

  

如果您将任何内容传递给next()函数(字符串&#39; route&#39;除外),则Express会将当前请求视为错误,并将跳过任何剩余的非错误处理路由和中间件函数。

您可以在创建路线时以这种方式使用上述功能:

const router = require('express').Router();

router
  .get('/my-route', middleware({
    uri: 'my-url'
  }), function(req, res, next) {

    // here you can access req.api_output.response & req.api_output.body
    // don't forget to call next once processing is complete
  });

现在,关于测试

在您的mocha测试框架中,您可以使用request模块调用API,就像在中间件函数中使用它来调用外部API一样。然后你可以轻松断言输出:

  1. 使用带有断言的try-catch来处理来自中间件功能的Error
  2. 使用普通断言来处理来自快速中间件的错误
  3. 注意我使用了术语&#34; express-middleware&#34;仅指代function(req, res, next) {}等中间件。我已经使用了术语&#34;中间件&#34;引用名为middleware的函数。

答案 2 :(得分:0)

middleware函数中,从try-catch返回一个promise。所以你的catch块会拒绝(错误)。

我会做这样的事情:

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