I have a failing Jest test case for code where I'm using promises. It looks like the resolution of the promise is happening after the test has completed, meaning I can't check that my promise resolution code has been executed.
It feels like I need to make the event loop tick so the promise is resolved and the resolution code is executed, but haven't found anything that can do that in Jest.
Here's a sample case. The code to be tested:
const Client = require('SomeClient');
module.exports.init = () => {
Client.load().then(() => {
console.log('load resolved');
setTimeout(() => {
console.log('load setTimeout fired, retrying init');
module.exports.init();
}, 1000);
});
};
Test code:
jest.useFakeTimers();
const mockLoad = jest.fn().mockImplementation(() => Promise.resolve());
jest.mock('SomeClient', () => {
return {
load: mockLoad
};
}, { virtual: true });
const promiseTest = require('./PromiseTest');
describe('SomeClient Promise Test', () => {
it('retries init after 10 secs', () => {
promiseTest.init();
expect(mockLoad).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledTimes(1); // <-- FAILS - setTimeout has not been called
jest.runAllTimers();
expect(mockLoad).toHaveBeenCalledTimes(2);
});
});
The expect(setTimeout).toHaveBeenCalledTimes(1);
assertion fails (setTimeout
has not been called at all), I think because the promise has not yet been resolved.
Am I doing something wrong here? Can I cause the event loop to tick inside the test?
To tick the event loop inside your test, you should make it asynchronous. A nice workaround was suggested on GitHub.
Having flushPromises
as suggested there
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
your test will look like
describe('SomeClient Promise Test', () => {
it('retries init after 10 secs', () => {
promiseTest.init();
expect(mockLoad).toHaveBeenCalledTimes(1);
// notice return so jest knows that the test is asynchronous
return flushPromises()
.then(() => {
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(mockLoad).toHaveBeenCalledTimes(2);
});
});
});
Or the same using async/await
:
describe('SomeClient Promise Test', () => {
it('retries init after 10 secs', async () => {
promiseTest.init();
expect(mockLoad).toHaveBeenCalledTimes(1);
await flushPromises();
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(mockLoad).toHaveBeenCalledTimes(2);
});
});
Not entirely sure why our React setup was different but Sergey's answer nearly got us there.
We needed these two bits in our test:
export function flushPromises(): Promise<void> {
return new Promise(jest.requireActual("timers").setImmediate);
}
await flushPromises();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With