-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
useEffect not triggering inside jest #215
Comments
After more testing, it appears that the working example is also not working :/ |
That's because React doesn't run the hook in sync mode. If you rerender the component the second time, it changes |
@sivkoff any idea why this happens and whether this will be fixed either in react or in react-testing-library? |
@wzrdzl, I'm not sure if it will be fixed, because it is a documented behavior:
More details in React Hooks reference: https://reactjs.org/docs/hooks-reference.html#timing-of-effects |
You might want to use the waitForElement or wait utils to asynchronously await the result of the hook, or try these auto-retrying queries if your environment is highly asynchronous https://github.com/alexkrolick/dom-testing-addon-async |
@sivkoff thanks for pointing to the docs, so as I understand the root cause of this problem is not really the "sync" test code. My test code looks like this and it also breaks without a re-render (I also tried to wait for assertion in a real it('should render a spinner after `showAfterDelay` milliseconds', () => {
jest.useFakeTimers();
// LoadingIndicator uses useEffect internal to schedule the rendering after a delay (setTimeout)
const { getByTestId, rerender } = render(<LoadingIndicator showAfterDelay={500} />);
// no spinner initially
expect(() => getByTestId(LOADING_SPINNER_CONTAINER_TEST_ID)).toThrow();
// now you need to rerender for React to run the `useEffect` hook
// this should hopefully be fixed or worked around with some new react-testing-library API
// this is the price you pay for riding the hype train :)
// https://github.com/kentcdodds/react-testing-library/issues/215#issuecomment-435592444
rerender(<LoadingIndicator showAfterDelay={500} />);
jest.advanceTimersByTime(500);
expect(getByTestId(LOADING_SPINNER_CONTAINER_TEST_ID)).toBeTruthy();
}); Looks like the issue is that the React's "determine paint event" logic doesn't work in |
Could it be that import {waitForDomChange} from 'dom-testing-library'
it('should render a spinner after `showAfterDelay` milliseconds', () => {
// LoadingIndicator uses useEffect internal to schedule the rendering after a delay (setTimeout)
const { getByTestId, rerender } = render(<LoadingIndicator showAfterDelay={500} />);
// no spinner initially
expect(() => getByTestId(LOADING_SPINNER_CONTAINER_TEST_ID)).toThrow();
// now you need to rerender for React to run the `useEffect` hook
// this should hopefully be fixed or worked around with some new react-testing-library API
// this is the price you pay for riding the hype train :)
// https://github.com/kentcdodds/react-testing-library/issues/215#issuecomment-435592444
await waitForDomChange()
expect(getByTestId(LOADING_SPINNER_CONTAINER_TEST_ID)).toBeTruthy();
}); |
@wzrdzl There is another simple workaround for your case — you just need to rerender it once again 😄 Let me explain how it works (probably I'm wrong):
I agree this workaround is ugly, but not sure if it makes sense to fix the case before React core team releases the stable version. Furthermore example with lifecycle alternative works well, so I think I've created a repo with the example so you can clone and make sure: |
You may also be interesting in http://kcd.im/hooks-and-suspense I have a video in there that explains one of the big nuances with testing effect hooks. I do plan on creating a small function called |
@kentcdodds special case handling for side-effectful components seems like it's letting implementation details shape the tests. What about adding EDIT: (where true is the default) |
If prefer that to just be the default. If we can make that work then let's do it. I just don't think it's possible |
I feel a bit dumb now. I spent some time writing an issue to linked RFC ⬆️ only to find out later that there only a single issue in this repo and it's exactly that problem 😅 Oh well... I am wondering, why using |
@FredyC, don't feel dumb 🤗 It's really odd, but I observed that |
I think the key part is when @kentcdodds explains that you need to rerender to get Anyways, thanks a lot! (I don't think React will change the way that |
Currently the way that I dislike the least for making this work nicely is to put this in my setup file: beforeAll(() => jest.spyOn(React, 'useEffect').mockImplementation(React.useLayoutEffect))
afterAll(() => React.useEffect.mockRestore()) I don't like it, but it's my favorite anyway... |
But are they exactly the same apart from don't blocking the rendering process? EDIT: Yes, they are. Per docs:
|
@kentcdodds Have you considering this workaround approach? Feels much easier imo ... facebook/react#14050 (comment) |
Basically the same except there's no way to clean up. So if you wanted to get the original |
For some reason, this workaround:
changes nothing to my Jest tests, effects just never happen. Only manual replacement of |
I'm actually starting to think that it's better to manually trigger effects. That's why I added the experiential |
Noooooooo. That would be an implementation detail. The user don't actually flush the effects. In fact, they don't even know what an effect isssss :/ |
@kentcdodds idk, it feels a little like the test having too much awareness of implementation details |
I agree, but at the same time, one could argue that running anything synchronously is an implementation detail. If we really wanted to be free of implementation details then everything we do with react-testing-library would be asynchronous. The thing is that if you force |
Is that an actual observable frame? I didn’t realize that, but then I’m not too familiar with concurrent react yet. Still, I’m having trouble imagining a case where I’d care to test that intermediate state. |
It can be. If you don't want it to be then you're either initializing your state incorrectly or you should use |
Options right now:
|
I think it would be best if we come out with something that's minimally helpful/opinionated just to enable testing at all. Then when we get real world experience with things we'll be better suited to solving this problem with the right abstraction. It's too early to form opinions. For that reason, I'm going to close this. We can bring it up again in ~6 months or so. |
The lack of coverage for my useEffect code led me here. It's been three months since the issue was closed, but I'm feeling the pain right now. I would absolutely love an automatic flushEffects feature. Without it, how am I supposed to test my side effects? I am not going to introduce timeouts into my tests. |
@alflennik https://github.com/threepointone/react-act-examples and please use latest RTL |
It seems the topic diverged a little bit but going back to the original issue. |
@matewilk Because you are testing implementation detail then? Seriously, just grab the awesome |
@FredyC I followed the documentation you have linked to, but no wrapping of Would be great to work together on this issue to get something more obvious working for tests which need to assert side effects on the DOM. At the moment anything in |
react-testing-library
version: 5.2.3react
version: 16.7.0-alpha.0node
version: CodeSandboxnpm
(oryarn
) version: CodeSandboxRelevant code or config:
What you did:
I'm trying to test functions with the
useEffect
hook inside jest.What happened:
The useEffect hook is not executed correctly inside a jest environment after calling
render(<Test />)
. However it appears to be working correctly if it is called directly in a browser environment.Reproduction:
Working example: https://codesandbox.io/s/l947z8v6xq (index.js)
Not working example in jest: https://codesandbox.io/s/7k5oj92740 (index.test.js)
Problem description:
The
useEffect
hook should have been executed after therender
callThe text was updated successfully, but these errors were encountered: