Skip to content

Commit 380ff32

Browse files
pierrezimmermannbampierrezimmermannmdjastrzebski
authored
fix: do not use a real timeout in waitfor when using fake timers (#1177)
* fix: do not use a real timeout in waitfor when using fake timers * refactor: rewrite test so that it is more explicit * refactor: make test faster by decreasing both interval and timeout * refactor: expect waitFor to throw rather than wrapping in try catch * test: fix typo in test comment * tests: increase a bit interval so test is more robust and make it more explicit * tests: some improvements from review * Update expect comment following review suggestion Co-authored-by: Maciej Jastrzebski <mdjastrzebski@gmail.com> Co-authored-by: pierrezimmermann <pierrez@nam.tech> Co-authored-by: Maciej Jastrzebski <mdjastrzebski@gmail.com>
1 parent 8e5c9f9 commit 380ff32

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

src/__tests__/waitFor.test.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,47 @@ test.each([false, true])(
204204
}
205205
);
206206

207+
const blockThread = (timeToBlockThread: number, legacyFakeTimers: boolean) => {
208+
jest.useRealTimers();
209+
let end = Date.now() + timeToBlockThread;
210+
211+
while (Date.now() < end) {
212+
// do nothing
213+
}
214+
215+
jest.useFakeTimers({ legacyFakeTimers });
216+
};
217+
218+
test.each([true, false])(
219+
'it should not depend on real time when using fake timers (legacyFakeTimers = %s)',
220+
async (legacyFakeTimers) => {
221+
jest.useFakeTimers({ legacyFakeTimers });
222+
const WAIT_FOR_INTERVAL = 20;
223+
const WAIT_FOR_TIMEOUT = WAIT_FOR_INTERVAL * 5;
224+
225+
const mockErrorFn = jest.fn(() => {
226+
// Wait 2 times interval so that check time is longer than interval
227+
blockThread(WAIT_FOR_INTERVAL * 2, legacyFakeTimers);
228+
throw new Error('test');
229+
});
230+
231+
await expect(
232+
async () =>
233+
await waitFor(mockErrorFn, {
234+
timeout: WAIT_FOR_TIMEOUT,
235+
interval: WAIT_FOR_INTERVAL,
236+
})
237+
).rejects.toThrow();
238+
239+
// Verify that the `waitFor` callback has been called the expected number of times
240+
// (timeout / interval + 1), so it confirms that the real duration of callback did not
241+
// cause the real clock timeout when running using fake timers.
242+
expect(mockErrorFn).toHaveBeenCalledTimes(
243+
WAIT_FOR_TIMEOUT / WAIT_FOR_INTERVAL + 1
244+
);
245+
}
246+
);
247+
207248
test.each([false, true])(
208249
'awaiting something that succeeds before timeout works with fake timers (legacyFakeTimers = %s)',
209250
async (legacyFakeTimers) => {

src/waitFor.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function waitForInternal<T>(
3838
let finished = false;
3939
let promiseStatus = 'idle';
4040

41-
const overallTimeoutTimer = setTimeout(handleTimeout, timeout);
41+
let overallTimeoutTimer: NodeJS.Timeout | null = null;
4242

4343
const usingFakeTimers = jestFakeTimersAreEnabled();
4444

@@ -64,6 +64,7 @@ function waitForInternal<T>(
6464

6565
// when fake timers are used we want to simulate the interval time passing
6666
if (fakeTimeRemaining <= 0) {
67+
handleTimeout();
6768
return;
6869
} else {
6970
fakeTimeRemaining -= interval;
@@ -90,6 +91,7 @@ function waitForInternal<T>(
9091
await new Promise((resolve) => setImmediate(resolve));
9192
}
9293
} else {
94+
overallTimeoutTimer = setTimeout(handleTimeout, timeout);
9395
intervalId = setInterval(checkRealTimersCallback, interval);
9496
checkExpectation();
9597
}
@@ -98,7 +100,9 @@ function waitForInternal<T>(
98100
done: { type: 'result'; result: T } | { type: 'error'; error: unknown }
99101
) {
100102
finished = true;
101-
clearTimeout(overallTimeoutTimer);
103+
if (overallTimeoutTimer) {
104+
clearTimeout(overallTimeoutTimer);
105+
}
102106

103107
if (!usingFakeTimers) {
104108
clearInterval(intervalId);

0 commit comments

Comments
 (0)