-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
Proposed improvement for jest fake timers implementation #8891
Comments
Thanks for the detailed issue!
That's correct.
If modules go out of their way to capture these (doing I'm not sure if this makes sense as part of core, it feels pretty invasive. I'm almost 100% sure people are gonna complain that they cannot capture real timers anymore. If you really want, you can use a custom test environment?
That's the way ES modules work - imports are always evaluated first (it's Babel's module transform which hoists them, not Jest) |
Hi @SimenB, Thank you for taking the time to consider my request. My first thought is that, in my tests, I do not want anything to be getting the real timer methods. However, I can see your point, if for example the wait-for-expect, or @testing-library/react wait command used fake timers that would not ideal... However you shouldn't mix async utilities with fake timers in the one test... But lets say hypothetically you had a module, which was not a production module being tested, rather a testing library for helping your test code, then I could see that you may want that to be unaffected by fake timers. However I have to say that we do not have that requirement and we have a fairly large project (126K lines of code, 600 test suites and 4023 tests). Also the workaround for this would be quite simple: import the test utility to receive real timers in the jest-before-env-setup.js. So I'm still of the opinion that perhaps this would be better solved by jest. I am interested to hear more about your suggestion of creating a custom test environment. How would this help and how would you approach this? Thanks again. 😊 Kind regards, |
I'm running into the same wall as @jackgeek did. My test suites are not working properly as |
To give an actual example supporting @SimenB's concerns, here's something I've used to flush promises when fake timers are being used: // How many times to flush before assuming an infinite loop.
const LOOP_LIMIT = 1000;
// Prevent useFakeTimers from overriding setTimeout below.
const setTimeout = window.setTimeout;
async function flushPromises() {
for (let i = 0; i < LOOP_LIMIT; i++) {
jest.runAllTimers();
await new Promise((resolve) => {
setTimeout(resolve, 0);
});
if (jest.getTimerCount() === 0) return;
}
throw new Error('infinite loooooop');
} It's possible |
Hi @markdascher, With my proposal if your utility was imported in jest-before-env-setup.js it would capture the real setTimeout. It could then be used in any test-suite and work as expected because the setTimeout captured in its first import (in the jest-before-env-setup.js) would be the real setTimeout. This feature request is quite old now and I have noticed that jest seems to behave slightly differently now: Regardless of whether you are have called useFakeTimers or useRealTimers or configured real or fake timers as defaults, if your test is async then real timers are always used. This is unfortunate as we still live in an environment where it is not possible to fake native promises. I have encountered situations, recently using https://mswjs.io/, where I want to use fake timers and have an async test. This is because msw is working in a separate thread and waiting on a promise is unavoidable. This forces all tests using msw to be async and now means they all run with real timers. 🤔 |
This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 14 days. |
This issue was closed because it has been stalled for 7 days with no activity. Please open a new issue if the issue is still relevant, linking to this one. |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
🚀 Feature Proposal
Not sure if this should be Feature Proposal. Please let me know if I have used the wrong issue template. This is really an enhancement suggestion for the jest fake timers.
The enhancement suggestion is this: Make
useFakeTimers
anduseRealTimers
work as expected in every scenario.Motivation
Presently the
jest.useFakeTimers
andjest.useRealTimers
methods lead to surprising results.This is due to how they are implemented. Here is my understanding of how they work:
jest-fake-timers
package.useFakeTimers
is called the mockable methods (e.g. setTimeout) are replaced on the global object with a fake implementationuseRealTimers
is called the mockable methods (e.g. setTimeout) are replaced with the captured real ones in Set rootDir for jest config #1The problem is that when the
useFakeTimers
oruseRealTimers
is called modules imported by the test suite may have already captured the mockable methods and will hold on to those references so that callinguseFakeTimers
oruseRealTimers
puts the suite into a weird state where some of the loaded code is using real timers and some fake. This is never desirable.I would like to propose that jest-fake-timers should instead always replace the mockable methods with a facade that will choose the correct implementation to defer to depending on the current desired state i.e. "real/fake".
This would avoid the aforementioned problem as everything would hold the references to the facade and the switch will not change the global object references.
Example of real developer experience when using the existing api
This is a cut-down real-world experience that my team and I had. Hopefully it illustrates why this change is important:
We set timers property in jest.config set to 'fake' as the majority of our code base requires fake timers.
However some tests need real timers. Here is how we attempted to solve this problem:
Attempt #1
Result
Attempt failed because foo and waitForExpect already had fake setTimeout
Attempt #2
Result
Attempt failed because foo and waitForExpect already had fake setTimeout
Attempt #3
Result
Attempt failed because foo and waitForExpect already had fake setTimeout
Wait! What!??
Yeah this is because
jest hoists importsES module imports are hoisted so foo module is executed before the jest.useFakeTimers() call...Attempt #4
foo.test.js:
/test-utilities/useRealTimers.js:
Result
Success! However now we realize that we cannot mix the use of real timers and fake timers in a single test-suite...
Sample facade implemention
Here is some pseudo code to illustrate how the facades could work:
let useFakeTimers = false;
jest.useFakeTimers = () => (useFakeTimers = true);
jest.useRealTimers = () => (useFakeTimers = false);
const realSetTimeout = global.setTimeout;
global.setTimeout = (...args) => (useFakeTimers ? fakeSetTimeout : realSetTimeout)(...args);
Example
Exactly as before except now you can safely use jest.useFakeTimers and jest.useRealTimers anywhere and the system will behave as expected.
Pitch
Why does this feature belong in the Jest core platform?
It is already in core.
The text was updated successfully, but these errors were encountered: