-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
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
What are the expectations around setTimeout/setImmediate after environment is torn down? #11202
Comments
cc @kentcdodds in case you've run into this with React Testing Library |
Thanks for the mention. I've run into these kinds of problems for sure. I like the proposed solution and it's what I do when I'm mocking timers and I would do it with real timers if I could. For example: https://github.com/kentcdodds/bookshelf/blob/main/src/setupTests.js#L53 |
Here's a few examples (all are reproducible with the default repl.it) This test suite passes: test('hello', () => {
setTimeout(() => {
throw Error('hi');
}, 0)
}); It's because the process exits before we've had a chance to run timeouts. However, this other test suite fails: test('hello', () => {
setTimeout(() => {
throw Error('hi');
}, 0)
});
// I just added this to make it fail:
test('oh no', async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
}); This is because the first timeout actually got a chance to run now, causing the crash. This seems like a source of non-determinism. Curiously, I can't reproduce this problem if I move these tests apart into different files, even if they run in band. I'm not sure why. Does it mean there's already some kind of a cleanup mechanism for For example, if I set |
Now regarding This seems to bring down the Worker entirely: test('hello', () => {
setImmediate(() => {
throw Error('hi');
})
}); I get This code also crashes Jest: test('hello', () => {
document.createElement('div');
setImmediate(() => {
document.createElement('div'); // Cannot read property 'createElement' of null
})
}); However, if I run multiple test files, of course, Note how test('hello', () => {
document.createElement('div');
setTimeout(() => {
document.createElement('div') // Works fine
}, 0)
}); |
Here's another fun curiosity. This code passes fine in Jest: /** @jest-environment jsdom */
test('hello', () => {
setTimeout(() => {
throw Error('hi')
}, 0);
}); However, if we switch to /** @jest-environment node */
test('hello', () => {
setTimeout(() => {
throw Error('hi')
}, 0)
}); This gives me This makes me think that maybe jsdom Jest environment does something special with timeouts in particular? |
Proposal to remove the inconsistency: #11204 |
Most of my reply at #11204 (comment) probably applies here instead 😅 I think long term, the ideal way for Jest to handle this is to disallow scheduling any work after the test environment has been torn down (or is in the process if being torn down) or run any previously scheduled work. Once a test has completed, no more of its code should run (mostly as stack traces and figuring out where this work was scheduled is confusing as we cannot connect it to. any specific test or test suite). This should probably happen for both "real" and "fake" timers, which'd mean we'd need to override all the timer functions manually to ensure we can track them. Whether or not that means to throw when it happens or just make it into a no-op is a separate discussion, but it can also be up to the environment itself (i.e. the envs shipped by Jest could throw, but some env
When you say "other file" does that mean another file loaded for the same test, or in a separate test? If the latter it's just because they get fresh globals each time. If the former... that sounds weird 😅 |
Yes, what I meant is that two different files "see" different |
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 30 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. |
We're running into some difficulties while making changes to React. I can explain them in more depth later, but the tldr is we tried to replace
setTimeout
withsetImmediate
in one place, and this led to a bunch of issues. Before diving deeper into that rabbit hole, I'm curious if there's any well-defined thinking about what should happen to timeouts/immediates after the Jest test environment is torn down.In particular, I'm thinking about a case where:
setTimeout
orsetImmediate
(no fake timers)jsdom
environmentI'm getting the impression is that Jest doesn't do anything special for real timers. Which would explain why, for example,
setTimeout
simply won't fire if this is the only test file, but would fire if the process is still running (e.g. due to async tests or the worker being reused for another test suite). Or whysetImmediate
s run afterjsdom
is already torn down, causing an error if we use DOM there.I want to understand if my explanation above is correct, and whether this behavior actually makes sense. Conceptually, a "real" Node environment is not isolated. E.g. module system isolation or jsdom teardown/setup is a Jest feature. It's not something built-in. By this logic, wouldn't it make sense to also isolate any "real"
setTimeout
andsetImmediate
between test files? For example, by consistently clearing them when the test environment is torn down instead of allowing "zombie" timeouts and immediates to execute. Or by forcing all immediates to run beforejsdom
is torn down.Any thoughts? I can prepare more detailed examples if this doesn't make sense.
The text was updated successfully, but these errors were encountered: