I am dealing with a code mixing node-style callbacks and Bluebird promises, and I need to write some unit tests for it.
In particular, cache.js
exposes the init()
function, which works with promises. It is then called by the doSomething()
function in another file (e.g. index.js
) which in turn accepts a callback that has to be invoked at the end of init()
.
Pseudocode is as follows:
// [ cache.js ]
function init() {
return performInitialisation()
.then((result) => return result);
}
// [ index.js ]
var cache = require('./cache');
function doSomething(callback) {
console.log('Enter');
cache.init()
.then(() => {
console.log('Invoking callback');
callback(null);
})
.catch((err) => {
console.log('Invoking callback with error');
callback(err);
});
console.log('Exit');
}
A possible unit test could be (showing only relevant code):
// [ index.test.js ]
...
var mockCache = sinon.mock(cache);
...
it('calls the callback on success', function(done) {
mockCache.expects('init')
.resolves({});
var callback = sinon.spy();
doSomething(callback);
expect(callback).to.have.been.calledOnce;
done();
});
This test passes, however changing the expectation to not.have.been.calledOnce
also passes, which is wrong.
Also, console logs are out of sequence:
Enter
Exit
Invoking callback
I have looked at several possibilities, none of which worked:
Using chai-as-promised, e.g. expect(callback).to.eventually.have.been.calledOnce;
Refactoring doSomething()
to be simply:
function doSomething(callback) { cache.init() .asCallback(callback); }
Can anyone help me understand what I am doing wrong and how I can fix it please?
console logs are out of sequence
The logs are in the correct order because your Promise
will be async meaning, at the very least, the internal console logs calls in then
& catch
will run on the next tick.
As to why the test is failing is the result of a couple of issues, first one is you don't appear to have sinon-chai
configured correctly, or at best your calledOnce
assertion isn't kicking in. Just to confirm, the top of your test file should something like:
const chai = require("chai");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
If you have that and it's still not working correctly then might be worth opening an issue on the sinon-chai
lib, however, a simple workaround is to switch to sinon assertions e.g.
sinon.assert.calledOnce(callback)
Secondly, when you do eventually fix this, you'll probably find that the test will now fail...everytime. Reason being you've got the same problem in your test that you have with your logging - your asserting before the internal promise has had a chance to resolve. Simplest way of fixing this is actually using your done
handler from Mocha as your assertion
mockCache.expects('init').resolves({});
doSomething(() => done());
In other words, if done
get's called then you know the callback has been called :)