I have searched both SO and Google and found a lot of similar questions and answers, but none of them seems to have helped me solve my issue.
I am attempting to write some test cases where I need to mock an async polling function. But no matter what I do I get:
Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout
I set up some minimal test cases the reproduce the problem:
jest.useFakeTimers();
describe('timers test', () => {
it('plain timer works as expected', () => {
const mock = jest.fn();
setTimeout(mock, 5000);
jest.runAllTimers();
expect(mock).toHaveBeenCalled();
});
it('Using a timer to mock a promise resolution results in a jest timeout error', async () => {
const mock = jest.fn(() => {
return new Promise((resolve) => setTimeout(resolve, 500));
});
const handler = jest.fn();
await mock().then(handler);
jest.runAllTimers();
expect(handler).toHaveBeenCalled();
});
it('Using a timer to mock a promise rejection results in a jest timeout error', async () => {
const mock = jest.fn(() => {
return new Promise((resolve, reject) => setTimeout(reject, 500));
});
const handler = jest.fn();
await mock().catch(handler);
jest.runAllTimers();
expect(handler).toHaveBeenCalled();
});
});
Can someone explain what I am doing wrong and why?
So with a follow up comment from @Bergi, I relaized the done
wasn't actually necessary either. I just needed to re-order some things. I then ran into a similar issue when testing chains of promises that further highlighted this so I added some cases for that.
jest.useFakeTimers();
describe('timers test', () => {
it('Using a plain timer works as expected', () => {
const mock = jest.fn();
setTimeout(mock, 5000);
jest.runAllTimers();
expect(mock).toHaveBeenCalled();
});
it('Using a timer to mock a promise resolution', async () => {
const mock = jest.fn(() => {
return new Promise((resolve) => setTimeout(resolve, 500));
});
const handler = jest.fn();
const actual = mock().then(handler);
jest.runAllTimers();
await actual;
expect(handler).toHaveBeenCalled();
});
it('Using a timer to mock a promise rejection', async () => {
const mock = jest.fn(() => {
return new Promise((resolve, reject) => setTimeout(reject, 500));
});
const handler = jest.fn();
const actual = mock().catch(handler);
jest.runAllTimers();
await actual;
expect(handler).toHaveBeenCalled();
});
it('Using a timer to mock a promise resolve -> delay -> resolve chain', async () => {
const mockA = jest.fn(() => {
return Promise.resolve();
});
const mockB = jest.fn(() => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 500);
});
});
const handler = jest.fn();
const actual = mockA()
.then(() => {
const mockProm = mockB();
jest.runAllTimers();
return mockProm;
})
.then(handler);
jest.runAllTimers();
await actual;
expect(mockA).toHaveBeenCalled();
expect(mockB).toHaveBeenCalled();
expect(handler).toHaveBeenCalled();
});
it('Using a timer to mock a promise resolve -> delay -> reject chain', async () => {
const mockA = jest.fn(() => {
return Promise.resolve();
});
const mockB = jest.fn(() => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500);
});
});
const handler = jest.fn();
const actual = mockA()
.then(() => {
const mockProm = mockB();
jest.runAllTimers();
return mockProm;
})
.catch(handler);
await actual;
expect(mockA).toHaveBeenCalled();
expect(mockB).toHaveBeenCalled();
expect(handler).toHaveBeenCalled();
});
});
@Bergi' comment led me to the solution. I ended up making use of the done
function, and removing the await
. This seems to work at least in this minimal test case.
jest.useFakeTimers();
describe('timers test', () => {
it('plain timer works as expected', () => {
const mock = jest.fn();
setTimeout(mock, 5000);
jest.runAllTimers();
expect(mock).toHaveBeenCalled();
});
it('Using a timer to mock a promise resolution results in a jest timeout error', async (done) => {
const mock = jest.fn().mockImplementation(() => {
return new Promise((resolve) => setTimeout(resolve, 500));
});
// make the handler invoke done to replace the await
const handler = jest.fn(done);
mock().then(handler);
jest.runAllTimers();
expect(handler).toHaveBeenCalled();
});
it('Using a timer to mock a promise rejection results in a jest timeout error', async (done) => {
const mock = jest.fn().mockImplementation(() => {
return new Promise((resolve, reject) => setTimeout(reject, 500));
});
// make the handler invoke done to replace the await
const handler = jest.fn(done);
mock().catch(handler);
jest.runAllTimers();
expect(handler).toHaveBeenCalled();
});
});