Search code examples
unit-testingpromisemocha.jssinonchai

Mocha test will not resolve promise using Sinon stub


I'm using Mocha to test a method that has an asynchronous method inside of it. I'm stubbing that dependency with Sinon, and returning a resolved promise. But the promise is never resolved, or at least when the assertion is run it hasn't resolved yet.

Here is the method under test

function identify(traits) {
  //THIS GETS CALLED SUCCESSFULLY
  userService.get().then(function(user){
    //CODE NEVER REACHES HERE

    userService.set(user).then(function(){
      //do something
    }, function(){
      //handle error
    });
  });
}

And here is the test

it('should identify a valid email address', function(){
  var user =  { email: '[email protected]' };
  var getUserStub = sinon.stub(userService, "get");
  var setUserStub = sinon.stub(userService, "set");
  var userReturn = { email: '[email protected]', urls: ['http://some.url.com'] };

  getUserStub.returns(Promise.resolve(userReturn));

  //THE METHOD UNDER TEST
  identifyController.identify(user);

  sinon.assert.calledOnce(userService.get);  //WORKS FINE
  sinon.assert.calledOnce(userService.set);  //FAILS
  getUserStub.restore();
});

The assertion on userService.set fails, it says it was called 0 times. What am I doing wrong?


Solution

  • I've finally found the problem.

    Promises are essentially asynchronous, but sinon calls are synchronous.

    See this code pen.

    What happens:

    1. You call identifyController.identify(user);
      • It will call get, which returns a promise, which is asyncrhonous.
    2. The main thread of the program will still be running, so your both sinon.assert.calledOnce calls will happen in sequence, synchronously

    By the time get resolves itself and calls set, because promises are non-blocking, the assertion will already have been executed, so it will fail.

    So, you can do like this:

    function identify(traits) {
      return userService.get().then(function(user){
        console.log('get');    
        userService.set(user).then(function(){
          console.log('set');
          //do something
        });
      });
    }
    

    And change this:

    identify(user).then(function(){
      sinon.assert.calledOnce(myObj.get);  //WORKS FINE
      sinon.assert.calledOnce(myObj.set);  //WORKS FINE NOW
    });