I am trying to test a queuing component that makes calls and handles a lot of scheduling. I want to test it with a mock api where the api responses are delayed as they would be in real life, but I want to use mock timers and fake the passage of time. In the following bare-bones example, the object under test is the Caller object.
function mockCall(): Promise<string> {
return new Promise<string>(resolve => setTimeout(() => resolve("success"), 20));
}
const callReceiver = jest.fn((result: string) => { console.log(result)});
class Caller {
constructor(call: () => Promise<string>,
receiver: (result: string) => void) {
call().then(receiver);
}
}
it("advances mock timers correctly", () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
expect(callReceiver).toHaveBeenCalled();
});
I would think this test should pass, but instead the expect is evaluated before the timer is advanced, so the test fails. How can I write this test so it will pass?
By the way, this test does pass if I use real timers and delay the expect for more than 20 milliseconds, but I am specifically interested in using fake timers and advancing time with code, not waiting for real time to elapse.
The reason is mockCall still returns Promise, even after you mocked timer. So call().then() will be executed as next microtask. To advance execution you can wrap your expect in microtask too:
it("advances mock timers correctly", () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
return Promise.resolve().then(() => {
expect(callReceiver).toHaveBeenCalled()
});
});
Beware of returning this Promise so jest would wait until it's done. To me using async/await it would look even better:
it("advances mock timers correctly", async () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
await Promise.resolve();
expect(callReceiver).toHaveBeenCalled();
});
Btw the same thing each time you mock something that is returning Promise(e.g. fetch) - you will need to advance microtasks queue as well as you do with fake timers.
More on microtasks/macrotasks queue: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
Jest repo has open proposal on handling pending Promises in more clear way https://github.com/facebook/jest/issues/2157 but no ETA so far.
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