Reputation: 9662
We have a simple wait method leveraging promises in our node app
exports.wait = (timeout) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, timeout)
});
};
We tried to test this behavior using sinon and chai.
We managed to get a proper assertion using chai-as-promised but it only check the promise resolves, without being able for us to test the real behavior:
The combination of promise with timers is really what gets us headaches.
Here is our last try setup:
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
chai.use(require('sinon-chai'));
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const wait = require('./wait').wait;
var clock;
before(() => {
clock = sinon.useFakeTimers();
});
after(() => {
clock.restore();
})
it('should not resolve until given time', (done) => {
const promise = wait(100);
let fulfilled = false;
promise.then(() => {
fulfilled = true;
done();
});
clock.tick(99);
expect(fulfilled).to.be.false;
clock.tick(2);
expect(fulfilled).to.be.true;
});
But fulfilled
is never flipped to true, or at least we cannot read it.
AssertionError: expected false to be true
How then mix timers with promise testing under chai - sinon to asset properly our timed resolve?
Upvotes: 2
Views: 1317
Reputation: 45780
You can test the code from the question like this:
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const wait = require('./wait').wait;
var clock;
before(() => {
clock = sinon.useFakeTimers();
});
after(() => {
clock.restore();
})
it('should not resolve until given time', async () => { // <= async
const promise = wait(100);
let fulfilled = false;
promise.then(() => {
fulfilled = true;
done();
});
clock.tick(99);
await Promise.resolve(); // let any pending Promise callbacks run
expect(fulfilled).to.be.false; // Success!
clock.tick(2);
await Promise.resolve(); // let any pending Promise callbacks run
expect(fulfilled).to.be.true; // Success!
});
Details
Fake timers turn callbacks scheduled with setTimeout
into synchronous calls.
Promise
callbacks, on the other hand, get queued in the PromiseJobs queue when the Promise
resolves, and don't run until after the current executing message has completed.
In this case, the currently running message is the test, so the then
callback that sets fulfilled
to true
doesn't run until after the test has completed.
You can use an async
test function and call await Promise.resolve();
at any point where you want to pause the currently running message and allow any queued Promise
callbacks to run.
For additional details about using Fake Timers with Promises
see this answer which uses Jest
, but the concepts are the same.
Upvotes: 3