Reputation: 5761
I'm writing a unit test which tests a function which waits a timeout before continuing, I've tried using
jest.useFakeTimers();
jest.advanceTimersByTime(20000);
but I keep getting an error message:
: Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
Module file:
async function main()
{
const promisesArray: Promise<void>[] = [];
// lots of logic and async calls //
for (const userRecord of records) {
promisesArray.push(verifyUserRecord(userRecord.userId));
}
await Promise.all(promisesArray);
}
function waitTimeout(ms: number) {
return new Promise((resolve) => setTimeout(() => resolve(true), ms));
}
async function verifyUserRecord(userId) {
await waitTimeout(CHECK_EXECUTION_STATUS_TIMEOUT);
await <some other method call>
/////////////// more logic /////////////
}
Test File
describe('main test', () => {
beforeAll(() => {
// calls to jest spy on //
});
beforeEach(() => {
jest.useFakeTimers();
// various mocks //
});
afterEach(() => {
jest.resetAllMocks();
});
afterAll(() => {
jest.useRealTimers();
jest.restoreAllMocks();
});
it('Should successfully run', async () => {
const p = main();
jest.advanceTimersByTime(60000);
jest.runAllTimers(); // <- explicitly tell jest to run all setTimeout, setInterval
jest.runAllTicks(); // <- explicitly tell jest to run all Promise callback
await p;
// validations //
});
});
But when I call main method from the test file, I get the error above.
When debugging I see that it arrives to waitTimeout
and then either waits or moves on to next it
in the test module.
It does not mock the timeout, and continues the module file logic.
How can I properly test the file and mock the timeout?
Thanks in advance.
Upvotes: 3
Views: 15270
Reputation: 21
I used the solution of Linden but i had to take a little care, i put the 'jest.spyOn' before the require of "my under test module"
something like this:
jest.spy(global, 'setTimeout').mockImplementation.....
// jest.spy before require
const mut = require('my_module_under_test');
In my case some submodule (used by my module under test), had a self call function that was using a 'setTimeout' to self recall it
something like this:
.....
function loop() {
// do something
setTimeout(loop, 1000);
}
loop();
.....
Upvotes: 0
Reputation: 802
Another way to mock setTimeout:
jest.spyOn(global, 'setTimeout').mockImplementation((callback) => {
if (typeof callback === 'function') {
callback();
}
return { hasRef: () => false } as NodeJS.Timeout;
});
Upvotes: 3
Reputation: 1571
You can use runOnlyPendingTimers
, this flushes all pending timers.
Usage
jest.useFakeTimers();
// call your function here
jest.runOnlyPendingTimers();
Upvotes: 0
Reputation: 2043
You use wrong API when mock timer
jest.setTimeout(20000);
is used to set the maximum timeout that jest will wait for a test to finish. If a test take to long jest will throw errors
Back to your problem, since you use jest.useFakeTimers();
you have to tell jest explicitly to exhaust all macro tasks (setTimeout) and micro tasks (Promise callback).
I modified your code a little so the expect works
const CHECK_EXECUTION_STATUS_TIMEOUT = 2000;
const records = [{ userId: 1 }, { userId: 2 }];
async function main() {
const promisesArray: Promise<number>[] = [];
for (const userRecord of records) {
promisesArray.push(verifyUserRecord(userRecord.userId));
}
return await Promise.all(promisesArray);
}
function waitTimeout(ms: number) {
return new Promise((resolve) => setTimeout(() => resolve(true), ms));
}
async function verifyUserRecord(userId: number) {
await waitTimeout(CHECK_EXECUTION_STATUS_TIMEOUT);
// await <some other method call>
/////////////// more logic /////////////
return userId;
}
describe("main", () => {
beforeEach(() => {
jest.useFakeTimers(); // <- use fake timer
});
afterEach(() => {
jest.useRealTimers();
});
it("should work", async () => {
const p = main();
jest.runAllTimers(); // <- explicitly tell jest to run all setTimeout, setInterval
jest.runAllTicks(); // <- explicitly tell jest to run all Promise callback
expect(await p).toEqual([1, 2]);
});
});
Upvotes: 3