-
Notifications
You must be signed in to change notification settings - Fork 108
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
feat: Add additional auto advance time controls #509
base: main
Are you sure you want to change the base?
Conversation
TL;DR This adds a new mode for automatically advancing time that moves more quickly than the existing shouldAdvanceTime, which uses real time. Testing with mock clocks can often turn into a real struggle when dealing with situations where some work in the test is truly async and other work is captured by the mock clock. In addition, when using mock clocks, testers are always forced to write tests with intimate knowledge of when the mock clock needs to be ticked. Oftentimes, the purpose of using a mock clock is to speed up the execution time of the test when there are timeouts involved. It is not often a goal to test the exact timeout values. This can cause tests to be riddled with manual advancements of fake time. It ideal for test code to be written in a way that is independent of whether a mock clock is installed or which mock clock library is used. For example: ``` document.getElementById('submit'); // https://testing-library.com/docs/dom-testing-library/api-async/#waitfor await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1)) ``` When mock clocks are involved, the above may not be possible if there is some delay involved between the click and the request to the API. Instead, developers would need to manually tick the clock beyond the delay to trigger the API call. This is different from the existing `shouldAdvanceTime` in the following ways: `shouldAdvanceTime` is essentially `setInterval(() => clock.tick(ms), ms)` while this feature is `const loop = () => setTimeout(() => clock.nextAsync().then(() => loop()), 0);` There are two key differences between these two: 1. `shouldAdvanceTime` uses `clock.tick(ms)` so it synchronously runs all timers inside the "ms" of the clock queue. This doesn't allow the microtask queue to empty between the macrotask timers in the clock whereas something like `tickAsync(ms)` (or a loop around `nextAsync`) would. This could arguably be considered a fixable bug in its implementation 2. `shouldAdvanceTime` uses real time to advance the same amount of real time in the mock clock. The way I understand it, this feels somewhat like "real time with the opportunity to advance more quickly by manually advancing time". This would be quite different: It advances time as quickly possible and as far as necessary. Without manual ticks, `shouldAdvanceTime` would only be capabale of automatically advancing as far as the timeout of the test and take the whole real time of the test timeout. In contrast, `setTickMode({mode: "nextAsync"})` can theoretically advance infinitely far, limited only by processing speed. Somewhat similar to the [--virtual-time-budget](https://developer.chrome.com/docs/chromium/headless#--virtual-time-budget) feature of headless chrome. In addition to the "quick mode" of `shouldAdvanceTime`, this also adds the ability to modify the initially configured values for shouldAdvanceTime and advanceTimeDelta.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #509 +/- ##
==========================================
+ Coverage 97.56% 97.59% +0.02%
==========================================
Files 16 18 +2
Lines 4430 4606 +176
==========================================
+ Hits 4322 4495 +173
- Misses 108 111 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Thanks for this, I will have to check out the code at a later time.
just to check ... You are aware of |
Yes, these are quite useful! Here are some of my thoughts where these still cause some test friction that this feature would help resolve: When mock clocks are installed, it's not "safe" to
|
Hi @fatso83, is there anything more you need from me here? We feel like this is quite a powerful feature, unlocking all the benefits of a fast mock clock without needing to change anything else about how a test is written. One might discover a slow test suite and only need to install an auto-ticking mock clock to speed things up. |
Sorry about that. Was hoping someone else on the maintainer team would pick it up, and I have simply forgotten about it 😄 I will have a new look tomorrow (and remind me if there's no update by next week), as I no longer remember the details ... |
Purpose (TL;DR)
This adds a new mode for automatically advancing time that moves more quickly than the existing shouldAdvanceTime, which uses real time. It also adds the ability to modify the initial values that were used for shouldAdvanceTime and advanceTimeDelta.
shouldAdvanceTime
is useful in some situations, but is not useful for solving the problem that testers usually install mock clocks for (to speed up time).Background
Testing with mock clocks can often turn into a real struggle when dealing with situations where some work in the test is truly async and other work is captured by the mock clock.
In addition, when using mock clocks, testers are always forced to write tests with intimate knowledge of when the mock clock needs to be ticked. Oftentimes, the purpose of using a mock clock is to speed up the execution time of the test when there are timeouts involved. It is not often a goal to test the exact timeout values. This can cause tests to be riddled with manual advancements of fake time. It ideal for test code to be written in a way that is independent of whether a mock clock is installed or which mock clock library is used. For example:
When mock clocks are involved, the above may not be possible if there is some delay involved between the click and the request to the API. Instead, developers would need to manually tick the clock beyond the delay to trigger the API call.
This is different from the existing
shouldAdvanceTime
in the following ways:shouldAdvanceTime
is essentiallysetInterval(() => clock.tick(ms), ms)
while this feature isconst loop = () => setTimeout(() => clock.nextAsync().then(() => loop()), 0);
There are two key differences between these two:
shouldAdvanceTime
usesclock.tick(ms)
so it synchronously runs all timers inside the "ms" of the clock queue. This doesn't allow the microtask queue to empty between the macrotask timers in the clock whereas something liketickAsync(ms)
(or a loop aroundnextAsync
) would. This could arguably be considered a fixable bug in its implementationshouldAdvanceTime
uses real time to advance the same amount of real time in the mock clock. The way I understand it, this feels somewhat like "real time with the opportunity to advance more quickly by manually advancing time". This would be quite different: It advances time as quickly possible and as far as necessary. Without manual ticks,shouldAdvanceTime
would only be capabale of automatically advancing as far as the timeout of the test and take the whole real time of the test timeout. In contrast,setTickMode({mode: "nextAsync"})
can theoretically advance infinitely far, limited only by processing speed. Somewhat similar to the --virtual-time-budget feature of headless chrome.In addition to the "quick mode" of
shouldAdvanceTime
, this also adds the ability to modify the initially configured values for shouldAdvanceTime and advanceTimeDelta.