Search code examples
node.jstestingpromisemocha.jsbluebird

Different Mocha behaviour - async/sync promises


I've recently encountered a strange behaviour. I have a big project that contains a lot of the tests and some of them were written in a synchronous way, assuming that the promise library is the one without deferred.

However, after preparing the environment on my machine (Mac OS X, nodeJS 0.12.18 - i know :( ), the tests seem to run with a different promise implementation - this time using async version with deferred and hence following test fails:

// Some generic mocking code here

instance.doSomething(); // This returns a promise

// Immediately after the previous call, we check the results and clean mocks
sinon.assert.called(request.Request);
request.Request.restore();

It started to work after being rewritten like this:

return instance.doSomething().then(function() {
  sinon.assert.called(request.Request);
  request.Request.restore();
});

To sum up, instance.doSomething performs two requests.

If the promise is called synchronously, request mock gets restored after both calls. If the promise is called asynchronously, first call succeeds but the second one fails, as in the meantime the stub was restored (before second call).

My questions are:

  • Is is possible that on my machine and CI, Mocha uses different promise implementations?
  • Is there a way to force promise implementation for Mocha?
  • Maybe the changed Promise comes from another place in the code?

All of this seems really strange, especially as the code uses bluebird as the main Promise library...


Solution

  • If you what you are testing won't be guaranteed to be in the correct state for testing until the promise is resolved, then you should write your test as you show in your 2nd snippet. That is the correct way to test conditions that depend on resolved promises. The fact that your initial code worked is due to luck. Consider the following code:

    const assert = require("assert");
    const Promise = require("bluebird");
    
    let moo = "initial";
    
    function someFunc() {
        return Promise.resolve()
            .then(function () {
                moo = "modified";
            });
    }
    
    beforeEach(() => moo = "initial");
    
    it("test one", () => {
        someFunc();
        assert.equal(moo, "modified");
    });
    
    it("test two", () => {
        return someFunc().then(() => {
            assert.equal(moo, "modified");
        });
    });
    

    The promise in someFunc is resolved immediately, but it does not matter. test one fails because I'm not waiting for the promise. It does not matter if I use Bluebird or Node's stock Promise implementation.

    There may be circumstances under which test one will pass, but that's just luck because promises do not guarantee that it will work. This luck may change, if:

    1. You switch to a different promise implementation.

    2. You run on a different platform. Promise implementations have to work with that the various platforms give them. And may thus behave somewhat differently from platform to platform, which is fine, so long as it does not violate the specs. However, the behavior your initial code depended on is not guaranteed by the specs so it may not be maintained on all platforms.

    3. A new version of the promise implementation you use is released and no longer maintains the behavior you were relying on.

    Is is possible that on my machine and CI, Mocha uses different promise implementations?

    Looking at Mocha's code I do not see any location where Mocha instantiate promises. It detects whether it returns a promise and depends on the API that promises provide but it does not create its own promises.

    Is there a way to force promise implementation for Mocha?

    See above. It receives the promises you return so it uses whatever implementation you use in your test suite.

    Maybe the changed Promise comes from another place in the code?

    Unsure what you mean there.