Unit testing 使用express validator的单元测试节点控制器/承诺

Unit testing 使用express validator的单元测试节点控制器/承诺,unit-testing,express,promise,sinon,Unit Testing,Express,Promise,Sinon,我正在使用“express validator”中间件包验证此exampleController端点的一些参数。在单元测试中,删除此控制器的最佳方法是什么?我经常会遇到这样的错误: TypeError: errors.isEmpty is not a function 路由器 var controller = require('./controllers/exampleController.js'); var express = require('express'); var router =

我正在使用“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' });
      });
    });
  });
});

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

好的,关于您显示的初始错误
TypeError:errors.isEmpty不是一个函数
,它不是由于您为stub
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()是一个顶级函数,您应该使用数组属性来存储错误列表

我附加了一个控制器和测试场景的改进版本,以便您可以将其与前面文章中介绍的最佳实践相关联

controller.js test.js 更新版本的测试中需要注意的几点

  • 与其手动构造请求/响应对象,不如使用已经存在的用于此作业的库。在我的版本中,我使用的是“”这在表达上几乎是一个标准
  • 测试控制器时,与其手动调用服务方法,不如通过模拟HTTP请求对象使用自然路由机制。通过这种方式,您可以覆盖快乐和悲伤的路由路径
  • 使用一个通用的HTTP req/res模拟库,意味着您的工作量会减少—您只需使用非标准函数扩展factory对象(例如getValidationResult()(来自express validator)并无缝添加间谍/存根
  • 最后,该库支持在响应事件上附加事件侦听器,否则无法手动模拟这些事件。在本例中,我们正在侦听响应对象中的end事件,该响应对象在
    返回res.status(400).json({error:'Bad Request')之后调用方法已在控制器中调用
希望我把事情弄清楚一点:)

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

  });

});