如何使用 Mocha、Chai 和 Sinon 正确测试 Express 控制器方法

How to properly test an Express controller method with Mocha, Chai, and Sinon

我刚开始使用 Sinon。我编写了以下测试,但它失败了,因为 res.status 总是返回未调用的结果。

import chai from 'chai';
import 'chai/register-should';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { db } from '../../models';
import * as loginController from '../../controllers/login';

chai.use(sinonChai);

describe('Login controller', () => {

  describe('post function', () => {
    let findOne, req, status, send, res;

    beforeEach(() => {
      findOne = sinon.stub(db.User, 'findOne');
      findOne.resolves(null);
      req = { body: { email: 'test@test.com', password: 'testpassword' }};
      status = sinon.stub();
      send = sinon.spy();
      res = { send: send, status: status };
      status.returns(res);
      loginController.post(req, res);
    });
    afterEach(() => {
      findOne.restore();
    });
    it('should return a 401 status for an invalid email', (done) => {
      res.status.should.be.calledWith(401);
      findOne.restore();
      done();
    });

  });
});

现在控制器中的方法非常简单。它首先使用 sequelize findOne 方法。如果找不到匹配的电子邮件,它应该抛出 401。这是它的样子:

export function post(req,res) {
  const email = req.body.email;
  const password = req.body.password;

  db.User.findOne({
    where: {email: email}
  }).then(user => {
    if (user) {
      // Other stuff happens here
    } else {
      res.status(401).send('That email address does not exist in our system.');
    }
  }).catch((error) => {
    res.status(500).send(error.message);
  });
}

当我 运行 测试时,它确实到达了应该返回状态的 else 语句,但是测试失败了,当我检查日志时,它说 res.status 不是'从来没有打过电话。

这里的问题是规范是同步的,没有考虑承诺。

出于可测试性原因,return 承诺是有意义的:

export function post(req,res) {
  ...
  return db.User.findOne(...)
  ...
}

如果路由处理程序是 async 函数,这可以很自然地完成。

由于 Mocha 支持承诺,规范也可以使用 async 函数而不是 done 回调:

it('should return a 401 status for an invalid email', async () => {
  const handlerResult = loginController.post(req, res);
  expect(handlerResult).to.be.a('promise');

  await handlerResult;
  res.status.should.be.calledWith(401);
});