-
Notifications
You must be signed in to change notification settings - Fork 272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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 #1177
Conversation
src/__tests__/waitFor.test.tsx
Outdated
const fibonaci = (n: number): number => { | ||
if (n === 0 || n === 1) { | ||
return 1; | ||
} | ||
|
||
return fibonaci(n - 1) + fibonaci(n - 2); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we replace this with something both more controllable and explicit:
const fibonaci = (n: number): number => { | |
if (n === 0 || n === 1) { | |
return 1; | |
} | |
return fibonaci(n - 1) + fibonaci(n - 2); | |
}; | |
function blockThread(milliseconds) | |
{ | |
var end = Date.now() + milliseconds; | |
while (Date.now() < end) { | |
// Intentionally do nothing | |
}; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good idea, it's way more explicit that way! I just had to switch to real timers when calling blockThread so that it works
src/__tests__/waitFor.test.tsx
Outdated
test.each([false, true])( | ||
'it should not depend on real time when using fake timers (legacyFakeTimers = %s)', | ||
async () => { | ||
jest.useFakeTimers({ legacyFakeTimers: false }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test does not seem to use each
parameter. Pls remove each param or apply it to legacyFakeTimers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh my bad, fixed it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pierrezimmermannbam I've reviewed the code the best I could, but I do not feel an expert in fake timers.
The changes in the waitFor.ts
file seem reasonable. Though I felt safer just sticking to the DTL code which we copied, but that might be just my poor understanding of fake timers ;-)
I've left some comments & suggestions about test code.
@thymikee pls take a look at this PR, you seem to know much more about fake timers than I.
5265fd3
to
e6471fe
Compare
@mdjastrzebski thanks for the review ! I believe I implemented all the changes you requested |
src/__tests__/waitFor.test.tsx
Outdated
|
||
const mockErrorFn = jest.fn(() => { | ||
// Wait 5 seconds so that check time is longer than interval | ||
blockThread(5, legacyFakeTimers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would wait a little bit longer to make the test more robust using the exact value might cause some race conditions
blockThread(5, legacyFakeTimers); | |
blockThread(8, legacyFakeTimers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I'm not using the exact value, the aim is that we block the thread for a time longer than the interval, but I'll make it a bit more explicit
src/__tests__/waitFor.test.tsx
Outdated
timeout: 6, | ||
interval: 2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You seem to be using very tight intervals, which could be fragile. Let use some interval in ~20-100 range (maybe 50 ms)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how using tight intervals could be fragile since we're using fake timers but maybe I'm thinking too much based in the current implementation so I increased it to 20
src/__tests__/waitFor.test.tsx
Outdated
// Verify that even though time to perform check is longer than interval | ||
// test won't timeout until number of checks * interval >= timeout | ||
// ie fake timers have been advanced by timeout when waitfor rejects |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Verify that even though time to perform check is longer than interval | |
// test won't timeout until number of checks * interval >= timeout | |
// ie fake timers have been advanced by timeout when waitfor rejects | |
// Verify that even though time to perform check is longer than interval | |
// test won't timeout until number of checks * interval >= timeout | |
// i.e. fake timers have been advanced (at least?) by timeout when waitFor rejects |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I start to understand this a bit more. I've left some comments for the testing code.
@mdjastrzebski thanks again for your feedbacks, I treated all your comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I think I finally grasped what is happening both in the code and in the test.
Good job @pierrezimmermannbam for catching it. I've suggested a rewrite to the expect comment to clarify the intent. Pls use/modify as you find it suitable.
@thymikee @AugustinLF pls take a look at this PR, as I do not feel expert on fake timers. |
Co-authored-by: Maciej Jastrzebski <mdjastrzebski@gmail.com>
@mdjastrzebski it looks good to me, but same, I'm not familiar with the timers part of the codebase. I'd suggest releasing this commit alone in a dedicated rc release to be able to do some testing on it, it's the type of changes that could bring some weird errors in some codebases. |
I've asked @thymikee for review, as our "fake timer specialist". That might delay merging for couple of days, but I think it's worth it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks!
@pierrezimmermannbam you might want to submit a similar bug/PR for DOM Testing Library, as they should have the same issue. |
Summary
Fixes #1176
Use a timeout in waitFor only when using realTimers and timeout with fake timers only after fakeTimeRemaining is over
Test plan
I added a test case that initially failed. I compute fibonaci suite so that it takes some time to perform the checks (it must take longer than interval for this bug to be visible)
This shouldn't be a breaking change, all tests that previously passed should still do because we're not performing any check after fakeTimeRemaining is over anyway