我如何存根 Promise 以便我的测试可以同步 运行 ?

How can I stub a Promise such that my test can be run synchronously?

我正在尝试通过存根其依赖项之一来对模块进行单元测试,在本例中为 UserManager

模块简化版如下:

// CodeHandler
module.exports = function(UserManager) {
  return {
    oAuthCallback: function(req, res) {
      var incomingCode = req.query.code;
      var clientKey = req.query.key;
      UserManager.saveCode(clientKey, incomingCode)
        .then(function(){
          res.redirect('https://test.tes');
        }).catch(function(err){
          res.redirect('back');
        }
      );
    }
  };
};

我正在对 UserManager 的 saveCode 函数进行存根,其中 returns 一个 Promise 这样它 returns 一个已解决的 Promise,但是当我 assert res.redirect 已被调用,可惜在断言时 res.redirect 尚未被调用。

单元测试的简化版本是:

// test
describe('CodeHandler', function() {
  var req = {
    query: {
      code: 'test-code',
      key: 'test-state'
    }
  };

  var res = {
    redirect: function() {}
  };

  var expectedUrl = 'https://test.tes';
  var ch;

  beforeEach(function() {
    sinon.stub(UserManager, 'saveCode').returns(
      new RSVP.Promise(function(resolve, reject){
        resolve();
      })
    );

    sinon.stub(res, 'redirect');

    ch = CodeHandler(UserManager);
  });

  afterEach(function() {
    UserManager.saveCode.restore();
    res.redirect.restore();
  });

  it('redirects to the expected URL', function(){
    ch.oAuthCallback(req, res);
    assert(res.redirect.calledWith(expectedUrl));
  })
});

我怎样才能正确地存根 promise 以使被测方法同步运行?

嗯,最简单的事情是 而不是 将它同步存根到 运行 因为这可能会改变执行顺序并使用 Mocha 的内置承诺支持(或jasmine-as-promised if using jasmine).

原因是可能会出现这样的情况:

somePromise.then(function(){
    doB();
});
doA();

如果您导致 promise 同步解决执行顺序 - 从而改变程序的输出,使测试毫无价值。

相反,你可以使用测试语法:

describe("the test", () => { // use arrow functions, node has them and they're short
    it("does something", () => {
        return methodThatReturnsPromise().then(x => {
           // assert things about x, throws will be rejections here
           // which will cause a test failure, so can use `assert`
        });
    });
});

您可以对单行使用更轻巧的箭头语法,从而使测试更加简洁:

describe("the test", () => { // use arrow functions, node has them and they're short
  it("does something", () =>
      methodThatReturnsPromise().then(x => {
         // assert things about x, throws will be rejections here
         // which will cause a test failure, so can use `assert`
      });
  );
});

据我所知,在 RSVP 中,您无法设置调度程序,因此无论如何都不可能同步测试事物,其他库(如 bluebird)允许您自行承担风险,但即使在允许您进行同步测试的库中也是如此这样做可能不是最好的主意。

我已经使用 sinon-stub-promise 找到了解决方案。

describe('CodeHandler', function() {
  var req = {
    query: {
      code: 'test-code',
      key: 'test-state'
    }
  };
  var ch;
  var promise;

  var res = {
    redirect: function() {}
  };

  beforeEach(function() {
    promise = sinon.stub(UserManager, 'saveCode').returnsPromise();
    ch = CodeHandler(UserManager);
    sinon.stub(res, 'redirect');
  });

  afterEach(function() {
    UserManager.saveCode.restore();
    res.redirect.restore();
  });

  describe('can save code', function() {
    var expectedUrl = 'https://test.tes';

    beforeEach(function() {
        promise.resolves();
    });

    it('redirects to the expected URL', function(){
      ch.oAuthCallback(req, res);
      assert(res.redirect.calledWith(expectedUrl));
    });
  });

  describe('can not save code', function() {
    var expectedUrl = 'back';

    beforeEach(function() {
        promise.rejects();
    });

    it('redirects to the expected URL', function(){
      ch.oAuthCallback(req, res);
      assert(res.redirect.calledWith(expectedUrl));
    })
  })
});

这非常有效。