node.jsunit-testingpromisestubbingrsvp-promise

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


I am trying to unit test a module by stubbing one of its dependencies, in this case the UserManager

A simplified version of the module is as follows:

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

I'm stubbing the UserManager's saveCode function which returns a Promise such that it returns a resolved Promise, but when I assert that res.redirect has been called, alas at the time of the assertion res.redirect has not yet been called.

A simplified version of the unit test is:

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

How can I properly stub the promise such that the method under test behaves synchronously?


Solution

  • I've worked out a solution using 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));
        })
      })
    });
    

    This works perfectly.