David Faizulaev
David Faizulaev

Reputation: 5761

Jest mock setTimeout - getting timeout error

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

Answers (4)

RATriches
RATriches

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

Linden X. Quan
Linden X. Quan

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

Aziza Kasenova
Aziza Kasenova

Reputation: 1571

You can use runOnlyPendingTimers, this flushes all pending timers.

Usage

jest.useFakeTimers();

// call your function here

jest.runOnlyPendingTimers();

Upvotes: 0

somallg
somallg

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]);
  });
});

Output: enter image description here

Upvotes: 3

Related Questions