【问题标题】:Unit test node controller/promises using express-validator使用 express-validator 进行单元测试节点控制器/承诺
【发布时间】:2017-05-01 18:08:34
【问题描述】:

我正在使用“express-validator”中间件包来验证此exampleController 端点的一些参数。为单元测试存根这个控制器的最佳方法是什么?我不断收到以下错误:

TypeError: errors.isEmpty is not a function

路由器

var controller = require('./controllers/exampleController.js');
var express = require('express');
var router = express.Router();

router.get('/example', controller.exampleController);

exampleController.js

exports.doSomething = function(req, res, next) {
  var schema = {
    'email': {
      in: 'query',
      isEmail: {
        errorMessage: 'Invalid Email'
      }
    },
    'authorization': {
      in: 'headers',
      // custom test
      isValidAuthToken: {
        errorMessage: 'Missing or malformed Bearer token'
      }
    }
  };

  // Validate headers/query params
  req.check(schema);

  // Handle response
  req.getValidationResult()
    .then(function(errors) {
      if (!errors.isEmpty()) {
        return res.status(400).json({ error: 'Bad Request' });
      } else {

        var context = {
          email: req.query.email,
        };

        return res.render('index', context);
      }
    })
};

测试

var chai = require('chai');
var sinonChai = require('sinon-chai');

chai.Should();
chai.use(sinonChai);
global.sinon = require('sinon');

var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);

var rewire = require('rewire');
var exampleController = rewire('../controllers/exampleController.js');

var errorsResponse = [{ 
  param: 'email',
  msg: 'Invalid Email',
  value: undefined
}];

describe('exampleController', function() {
    var req;
    var res;

    beforeEach(function() {
      req = {
        headers: {},
        query: {},
        check: sinon.spy(),
        getValidationResult: sinon.stub().returnsPromise()
      };
      res = {
        status: sinon.stub().returns({
          json: json
        }),
        render: sinon.spy()
      };
    });

    afterEach(function() {
      req.query = {};
    });

    context('when missing email query param', function() {
      beforeEach(function() {
        req.getValidationResult.resolves(errorsResponse);
        exampleController.doSomething(req, res);
      });

      it('should call status on the response object with a 400 status code', function() {
        res.status.should.be.calledWith(400);
      });

      it('should call json on the status object with the error', function() {
        json.should.be.calledWith({ error: 'Bad Request' });
      });
    });
  });
});

【问题讨论】:

    标签: unit-testing express promise sinon


    【解决方案1】:

    您构建用于验证控制器的单元测试的方式并不一致。我将尝试向您详细介绍问题和解决方法,但在我们继续之前,请先查看unit testing Express controllers 上的这篇精彩文章。

    好的,所以关于您提出的初始错误 TypeError: errors.isEmpty is not a function,这是由于您为存根 getValidationResult() 方法而设置的格式错误的响应对象。

    从这个方法打印出一个示例响应对象后,您会注意到正确的结构是这样的:

    { isEmpty: [Function: isEmpty],
      array: [Function: allErrors],
      mapped: [Function: mappedErrors],
      useFirstErrorOnly: [Function: useFirstErrorOnly],
      throw: [Function: throwError] }
    

    而不是您的响应版本:

    var errorsResponse = [{ 
      param: 'email',
      msg: 'Invalid Email',
      value: undefined
    }];
    

    isEmpty() 是一个顶级函数,您应该使用 array 属性来存储错误列表。

    我附上了你的控制器和测试场景的改进版本,以便你可以将其与上述文章中介绍的最佳实践相关联。

    controller.js

    var express = require('express');
    var router = express.Router();
    
    router.get('/example', function(req, res) {
      var schema = {
        'email': {in: 'query',
          isEmail: {
            errorMessage: 'Invalid Email'
          }
        }
      };
    
      // Validate headers/query params
      req.check(schema);
    
      // Handle response
      req.getValidationResult()
        .then(function(errors) {
    
          if (!errors.isEmpty()) {
    
            return res.status(400).json({
              error: 'Bad Request'
            });
          } else {
    
            var context = {
              email: req.query.email,
            };
    
            return res.render('index', context);
          }
        });
    });
    
    
    module.exports = router;
    

    test.js

    'use strict';
    
    const chai = require('chai');
    const sinon = require('sinon');
    const SinonChai = require('sinon-chai');
    
    var sinonStubPromise = require('sinon-stub-promise');
    sinonStubPromise(sinon);
    
    chai.use(SinonChai);
    chai.should();
    
    var mockHttp = require('node-mocks-http');
    
    var controller = require('./controller.js');
    
    describe.only('exampleController', function() {
    
      context('when missing email query param', function() {
    
        var req;
        var res;
    
        beforeEach(function() {
    
          // mock the response object
          // and attach an event emitter
          // in order to be able to
          // handle events
          res = mockHttp.createResponse({
            eventEmitter: require('events').EventEmitter
          });
    
        });
    
        it('should call status on the response object with a 400 status code',
          (done) => {
    
            // Mocking req and res with node-mocks-http
            req = mockHttp.createRequest({
              method: 'GET',
              url: '/example'
            });
    
            req.check = sinon.spy();
    
            var errorsResponse = {
              isEmpty: function() {
                return false;
              },
              array: [{
                param: 'email',
                msg: 'Invalid Email',
                value: undefined
              }]
            };
    
            // stub the getValidationResult()
            // method provided by the 'express-validator'
            // module
            req.getValidationResult = sinon.stub().resolves(errorsResponse);
    
            // spy on the response status
            sinon.spy(res, 'status');
            sinon.spy(res, 'json');
    
            // called when response
            // has been completed
            res.on('end', function() {
              try {
                // assert status and JSON args
                res.status.should.have.been.calledWith(400);
                res.json.should.have.been.calledWith({error: 'Bad Request'});
                done();
              } catch (e) {
                done(e);
              }
            });
    
            // Call the handler.
            controller.handle(req, res);
          });
    
      });
    
    });
    

    在更新版本的测试中需要注意的几点。

    • 与其手动构造请求/响应对象,不如使用已经为这项工作提供的库。在我的版本中,我使用的是“node-mocks-http”,这几乎是 Express 的标准。
    • 在测试控制器时,与其手动调用服务方法,不如通过模拟的 HTTP 请求对象使用自然路由机制。这样你就可以覆盖快乐和悲伤的路由路径
    • 使用一个通用的 HTTP req / res 模拟库,意味着更少的工作 - 你需要做的就是使用非标准函数扩展工厂对象(例如 getValidationResult() 来自 express-validator)并无缝添加您的间谍/存根
    • 最后,该库支持在响应事件上附加事件侦听器,否则您无法手动模拟。在此示例中,我们正在侦听响应对象中的 end 事件,该对象是在您的控制器中调用 return res.status(400).json({error: 'Bad Request'}); 方法之后调用的。

    希望我已经澄清了一点:)

    【讨论】:

      猜你喜欢
      • 2015-04-30
      • 2016-07-15
      • 2015-03-15
      • 1970-01-01
      • 2016-07-25
      • 2016-10-25
      • 2018-06-02
      • 1970-01-01
      • 2017-02-27
      相关资源
      最近更新 更多