How do I replace the setTimeout method in a typesafe manner?

124 views Asked by At

In my tests I want to replace the setTimeout method so that the tests run fast.

Originally I had the equivalent piece of code that used "any"... But of course eslint etc bleats.

I now have this piece of code which at last does not have any in it.

  let defaultTimeout: typeof globalThis.setTimeout;
  beforeEach(() => {
    defaultTimeout = global.setTimeout;
    globalThis.setTimeout = (callback: TimerHandler) => { (1)
      defaultTimeout(callback, 500);
    };
  })

However, I still get an error at globalThis.setTimeout

TS2741: Property  promisify  is missing in type  (callback: TimerHandler) => void  but required in type  typeof setTimeout  timers.d.ts(161, 19):  promisify  is declared here.

I know that I can resolve this by using any and unknown... is there another way?

2

There are 2 answers

1
Tal Rofe On BEST ANSWER

Overriding functions like setTimeout, setInterval, console.* is bad practice. Why do you do it in that way? Did you consider using timer mocks?

I assume you use Jest or Vitest, anyway the logic is identical.

If all you want is to skip the timers, you could do

jest.useFakeTimers();

// Your test code goes here...

expect(true).toEqual(true);

jest.runAllTimers();

jest.useRealTimers();

The jest.runAllTimers(); line is the one doing the trick, "fast-forwarding" the timers, read more here: https://jestjs.io/docs/jest-object#jestrunalltimers

0
Michael Wiles On

Thanks to the impetus from @Tal Rofe I had another look:

I didn't realise I can just use jest to mock global functions... so this is my solution:

jest.useFakeTimers();
type TimeoutType = ReturnType<typeof globalThis.setTimeout>;
const spyOn = jest.spyOn(global, 'setTimeout');
spyOn.mockImplementation((callback: (args: void) => void): TimeoutType => {
  callback();
  return <TimeoutType>(<unknown>0); // timeout is actually a number!
});