-
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
[5.5.0] Getting warning about "act" anyway #281
Comments
I can't see your tests, but I'm guessing you're triggering some changes 'manually' (ie - without fireEvent). those calls should be wrapped with |
Funny enough there is not a single I guess I will wait when you can have a look at those test files because I am not sure how to handle this reality. For a reference, a test file like this is causing the warning. https://github.com/mobxjs/mobx-react-lite/blob/master/test/ObserverComponent.test.tsx |
Just got bitten by this, upgraded to 5.5.0, remove EDIT to include the test: import { render, fireEvent } from 'react-testing-library';
// Other imports and tests
it('sets and reacts to data', () => {
const w = render(
<FieldWrapper>
<FieldInput id="foo" />
</FieldWrapper>,
);
fireEvent.change(w.getByTestId('foo'), {
target: { value: 'bar' },
});
expect(w.getByTestId('foo')).toHaveAttribute('value', 'bar');
}); |
A codesandbox would be very helpful. |
yeah I can't help more without a working example, sorry, basically shooting blind. |
Yeah, that change event basically also trigger bunch of effects (async processing) behind the scene, which when resolved updates other state. I tried to simulate it in this sandbox https://codesandbox.io/s/rjnwr62v5o The component is in |
Gotcha, so what @threepointone said is true:
So it's working as designed. I'm honestly not sure how to account for situations like this though. What do you think Sunil? |
I am kinda unclear what "manual update" means. Is invoking a setter from the |
Heading home so I’ll get to this in a bit, but tldr - jest.useFakeTimers/jest.runAllTimers should help. Back in an hour. |
Regarding the call stack. Any time you call a state updater function like Anyway, the We need to find a good way to either work around this and/or document this. Note: This has nothing to do with react-testing-library or even hooks and everything to do with new warnings that were introduced in React@16.8.0 to make your tests better resemble reality. As I said, I'm not sure what the difference is. Maybe Sunil can help explain it. |
I've prepared another code sandbox reproducing the issue. The warning comes from the first test. The second test is failing and the last one is only one working properly, but super ugly and I really hope there will be a better way. |
@FredyC I got it working for the first test without warning: it("works, but shows warning", () => {
const state = mobx.observable({ value: 5 });
const { container } = render(<TestComponent state={state} />);
expect(container.textContent).toBe("5");
// This updates state, so should be wrapped in act
act(() => {
state.value = 10;
})
expect(container.textContent).toBe("10");
}); AFAIK, this is consistent by the |
^ this is correct. I’ll edit the docs to be clearer to say that you need to wrap anything that causes updates to be wrapped. |
Ah, now that is starting to make sense, but it's ugly nonetheless considering that this was working just fine pre-16.8. I was kinda hoping that with the official release there will be an improvement to testing experience, not that it will be even worse 😢 |
Hmmm... I'm still not certain how to solve for cases like this: // this is similar to __local_tests__/async-with-mock.js
// except this one uses a form of dependency injection because
// jest.mock is not available in codesandbox
import React from 'react'
import axios from 'axios'
import {render, fireEvent, waitForElement, cleanup} from 'react-testing-library'
import 'jest-dom/extend-expect'
afterEach(cleanup)
function useFetch({url, axios = axios}) {
const [state, setState] = React.useState({})
const fetch = async () => {
const response = await axios.get(url)
setState(s => ({...s, data: response.data}))
}
const ref = React.useRef()
React.useEffect(
() => {
if (ref.current) {
ref.current = true
} else {
fetch()
}
},
[url],
)
return {state, fetch}
}
function Fetch({url, axios}) {
const {state, fetch} = useFetch({url, axios})
const {data} = state
return (
<div>
<button onClick={fetch}>Fetch</button>
{data ? <span data-testid="greeting">{data.greeting}</span> : null}
</div>
)
}
test('Fetch makes an API call and displays the greeting', async () => {
const fakeAxios = {
get: jest.fn(() => Promise.resolve({data: {greeting: 'hello there'}})),
}
const url = 'https://example.com/get-hello-there'
const {getByText, getByTestId} = render(<Fetch url={url} axios={fakeAxios} />)
fireEvent.click(getByText(/fetch/i))
const greetingNode = await waitForElement(() => getByTestId('greeting'))
expect(fakeAxios.get).toHaveBeenCalledTimes(2) // React runs the render twice in dev mode
expect(fakeAxios.get).toHaveBeenCalledWith(url)
expect(greetingNode).toHaveTextContent('hello there')
}) |
I get the intentions though, by avoiding the warning we can be sure that the state update is intended. Same with But then the |
And another use case where it will be even more annoying. let oldTodo: any // nooooo, I am too sexy for this :(
act(() => {
oldTodo = store.todos.pop()
})
expect(getDNode(oldTodo, "title").observers.size).toBe(0) And in the TypeScript world this means falling back to
That might be true, but those warnings don't really give a useful hint where in the code is the problem happening. It would need to be improved for sure. |
are you not seeing a stack trace under your warning? just got home, working on your fetch example @kentcdodds |
Consider that this will make your tests more reliable. This is apparent most when you fire multiple events in a row, effects cascade (or even just get triggered). I wouldn't be surprised if fixing all the warnings leads to discovering actual bugs in your code. That was my experience when doing the same for fb. |
Not really, you can see the output in code sandboxes presented here or in my linked Travis build. Jest is only showing the line where console.error got called: A good thing is I've managed to fix mobx-react-lite tests and it working just fine. I guess in time it will become a habit. |
I'm still seeing the Here is what one of my tests look like for a context provider:
The tests succeeds, however I'm seeing the console warn, any suggestions / ideas as to why this is happening? Please let me know if I can provide any further clarification. |
It should working:
I've got a problem with fetch and catch errors. |
So I'm currently updating the tests for useAsync to work with 16.8, hitting this exact problem. I tried using test("returns render props", async () => {
jest.useFakeTimers()
const promiseFn = () => new Promise(resolve => setTimeout(resolve, 0, "done"))
const component = <Async promiseFn={promiseFn}>{({ data }) => data || null}</Async>
const { getByText } = render(component)
act(() => {
jest.runAllTimers()
})
await waitForElement(() => getByText("done"))
}) Wrapping stuff with |
just a heads up, I've been wanting to answer this in detail, but just swamped with a bunch of other things with the team. we're aware of the problems, promise. I'll ping here when I have something to share. |
Good to know you're aware. I trust you'll come up with a good solution. Keep up the good work! |
tldr - you should use async act around asynchronous operations, and careful that your yarn lockfile hasn't locked you into an older version of react. |
@threepointone Thank you so much for your help! |
Since it looks like it will take a while until React DOM gets updated, what do you think of adding this to the README? It seems too relevant to be tucked away in an issue. |
Feel free to open a PR to add it somewhere, but I have it on good authority that we're a week or two away from the 16.9.0 release. facebook/react#16254 |
Can confirm that this issue is resolved using My components: const MyComponent = props => {
const [num, setNum] = React.useState(0)
React.useEffect(() => {
SomeAPI.fetch().then(() => {
setNum(10)
})
}, [])
return <div>{num}</div>
} RTL Test: it('should do something and expect something', async () => {
mockAdapter
.onGet('/someapi/fetch/')
.replyOnce(() => [200, {}])
const { debug, container } = render(<MyComponent />)
await flushAllPromises()
expect(true).toBe(true)
}) |
Hi! i have a question. How to install the version |
🎉 This issue has been resolved in version 8.0.9 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
react@16.9.0 has been released 👏 @threepointone!! So as long as you ensure you're using the latest version of |
Hi @kentcdodds , I'm still having the issue for some reason:
Even all the test are green, I'm still getting the console error:
I updated all the dependencies: React 16.9, RTL 9.1.0, etc. and still have the error. Any clue on why is happening? Is related to Apollo? Or just my fault 😄 ? Thanks! |
@Yagogc, the code that causes the state update is the |
@Yagogc Generally I would recommend you the following instead of // wait imported from RTL as @ChristianBoehlke mentioned
await wait(() => {
expect(getByText('P1 W1')).toBeDefined()
}) That way RTL will keep trying until the expectation passes (or timeouts). It's more natural than waiting for some explicit timers imo. |
Hey @ChristianBoehlke thank you so much, that works, at least for the second test. Now the first test is still passing but throwing a different error:
If I make it async/await the error goes away but the test fails. And if I don't use the wait it will error about not using act... Any ideas? |
It's probably best to use the findBy* queries. Then the waiting is built in. |
@Yagogc And why don't you do what it's telling you to do? :) You need to Or use |
Wouldn't something like this do the trick?
|
Sorry @FredyC , I didn't saw your comment earlier. I have updated the second test with the change you mentioned, but for the first test I had to use the findBy* query that @bopfer mentioned. Thanks both by the way. Right it is working like this:
Now I have a question: why I can in the finBy* query on the first test but not on the second test? If instead I just do: |
Your first test is false positive because expect(promise).toBeDefined() If you await the |
This: await wait(() => {
expect(getByText('P1 W1')).toBeDefined()
}) Can also be completed replaced with this: await findByText('P1 W1') The getBy* and findBy* queries will all throw on failure and the test will fail. The |
@bopfer I tend to do that sometimes as well, but it's somewhat weird to see a test without an actual Anyway, I think someone shall lock this long term issue to make people open specific new issues. Note for passers-by: As long as you are using RTL helpers and functions, you most likely won't need to worry about |
Thanks for the fast work Kent, but seems something is still off. I've updated to 5.5.0 and tests are still generating same act warnings. Am I missing something here? Do we need to actually use
act
inside the tests?https://travis-ci.org/mobxjs/mobx-react-lite/builds/489613981
It's curious that I've actually removed a bunch of
flushEffects
calls which were necessary before and tests are passing just fine. Clearlyact
helped with that. And yet those warnings shouldn't be there. 🤔The text was updated successfully, but these errors were encountered: