对结束承诺链的函数进行单元测试

Unit test a function that ends the promise chain

假设我在一个名为 UserController 的 class 中有一个函数,它按照这些行做一些事情(其中 userService.createUser() returns 一个承诺):

function createUser(req, res)
{
  const userInfo = req.body;

  userService.createUser(userInfo)
    .then(function(){res.json({message: "User added successfully"})})
    .fail(function(error){res.send(error)})
    .done();
}

我如何测试,当承诺解决时,调用 res.json(),当承诺拒绝时,调用 res.send(error)

我试过写这样的测试:

const userService = ...
const userController = new UserController(userService);
const response = {send: sinon.stub()};

...

const anError = new Error();
userService.createUser = sinon.stub().returns(Q.reject(anError));

userController.createUser(request, response);

expect(response.send).to.be.calledWith(anError);

但是测试失败 "response.send is never called"。我还尝试在调用 res.send(error) 之前记录一些东西,并且记录确实发生了。

我的猜测是 expect() 在执行 res.send(error) 之前被调用,因为它是异步的。

我对 promises 和单元测试还很陌生,这是我的架构问题还是我对 promises 的使用?

我将 Q 用于 promises,将 mocha、chai、sinon 用于我的单元测试。

当你有一个异步调用时,expect 语句在 userController.createUser() 行之后被调用。所以当评估断言时它还没有被调用。

要异步测试您的代码,您需要在 it 语句中声明 done,然后手动调用它以获取结果。

在你的测试文件中:

it('should work', function(done) {
  ...
  userController.createUser(request, response);

  process.nextTick(function(){
    expect(response.send).to.be.calledWith(anError);
    done();
  });
});  

这将使 Mocha(我假设您正在使用它)在调用 done() 时评估您的 excpect

或者,您可以在 UserController.createUser 函数上设置一个 cb 函数,然后在 .done() 上调用它:

用户控制器

function createUser(req, res, cb) {
  const userInfo = req.body;

  userService.createUser(userInfo)
    .then(function(){res.json({message: "User added successfully"})})
    .fail(function(error){res.send(error)})
    .done(function(){ if(cb) cb() });
  }

然后在你的测试中:

userController.createUser(request, response, function() {
  expect(response.send).to.be.calledWith(anError);
  done();
});

假设您使用 Mocha 或 Jasmine 作为框架,更简单的方法是从刚开始时继续,但完全跳过 Sinon(因为这里不需要它,除非您测试收到的实际参数):

// observe the `done` callback - calling it signals success
it('should call send on successful service calls', (done) => {
  // assuming  same code as in question
  ... 

  const response = {send: done};
  userController.createUser(request, response);
});


// observe the `done` callback - calling it signals success
it('should call send on failing service calls', (done) => {
  // assuming  same code as in question
  ... 

  const response = {send: err => err? done(): done(new Error("No error received"))};
  userController.createUser(request, response);
});

披露:我是 Sinon 维护团队的一员。