-
Notifications
You must be signed in to change notification settings - Fork 273
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
Error thrown - Warning: You called act(async () => ...) without await. #379
Comments
This particular line looks a bit odd to me: await waitFor(() => expect(container.getByTestId('recordTypePicker')).toBeTruthy()) I don't thing we support putting jest's Could you try something like: const element = await findByTestId('record...') // This is essentially waitFor & getByTestId
expect(element).toBeTruthy() Let me know if you still got the same error. |
@mdjastrzebski import * as React from 'react';
import { fireEvent, render } from 'react-native-testing-library';
import { NewSObjectContainer } from '../NewSObject';
describe('NewSObjectContainer', () => {
const setup = () => {
const route = { params: { sobject: 'Account' } };
const navigation = { setOptions: jest.fn() };
const container = render(<NewSObjectContainer route={route} navigation={navigation} />);
return { container };
};
it('should render a NewSObjectContainer - with Record Type picker exposed', async () => {
const { container } = setup();
const input = await container.findByTestId('recordTypePicker');
expect(input).toBeTruthy();
expect(container.toJSON()).toMatchSnapshot();
});
it('should render a NewSObjectContainer - with page layout exposed', async () => {
const { container } = setup();
const input = await container.findByTestId('recordTypePicker');
expect(input).toBeTruthy();
fireEvent(input, 'onChange', { nativeEvent: { selectedSegmentIndex: 1 } });
const layout = await container.findByTestId('newSObjectLayout');
expect(layout).toBeTruthy();
expect(container.toJSON()).toMatchSnapshot();
});
}); |
@jpmonette can you provide a repro repo? |
@mdjastrzebski I ran into a similar issue (although, don't know if it is exactly the same problem) and made a mcve here: https://github.com/jsamr/react-native-testing-library-379 |
I also ran into same problem after upgrading to v7.0.1. |
@jsamr thanks for posting repo, I'll try to reproduce that tomorrow. |
@jsamr You should wrap the act(() => {
result.instance.reload();
}); As each state mutation should be wrapped in This leaves your code with following warning logged to console
which is simingly caused by |
@mdjastrzebski , |
I'm facing the same error but my test is different. Before moving from version 2 to 7 my test was the same and I have no errors, but after updating to v7 i've been getting the same error. I have something really simple like this: const { getByText, update } = render(<Component />);
fireEvent(getByText('idx-1'), 'onPress');
await act(async () => {
update(<Component />);
}); I use Warning: An update to ForwardRef(NavigationContainer) inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */ UPDATE: 11-08-2020 I'm adding a variable to know if you component is mounted/unmounted. With this variable validate wether update or not the state of the componente, which cause to re-render the component. // I prefer to use isMounted. This way I don't have to use a negation to validate if something is `true` when is `false` :/
useEffect(() => {
let isMounted = true;
(async ( ) => {
const result = await someAsyncMethod();
// Here i want to update the state of my component
if (isMounted) {
setState(result);
}
})();
return () => {
isMounted = false;
};
}) My test now it is like this: it('test', () => {
const { getByText, update } = render(<Component />);
fireEvent(getByText('idx-1'), 'onPress');
act(() => {
update(<Component />);
});
}); Hope this might help :) One last thing! Don't forget to use the |
I’ve noticed that this error/warning happens when I use more than 1 |
Same issue here. |
Same issue and I'm not doing anything in my test, just rendering and await findBy |
I suggest use wait-for-expect directly. |
I guess this is the issue, too. In my case, those messages disappeared when I replaced |
Any updates here? This is happening when i use more than 1 |
I patched if(!argsWithFormat[0].includes(/You called act\(async \(\) => ...\) without await/)) {
Function.prototype.apply.call(console.error, console, argsWithFormat);
} |
I am getting this without having any awaits or findBy's, this is the simplest example I could reproduce it, the error appears when I get to the second changeText
Not entirely sure what I need to do? |
I've dug into this a bit and I believe the issue is race conditions. Here's the act code and the block where the console log happens: https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L3680 You'll notice this just creates a resolved promise and logs in a then block (2 next ticks). To ignore the error, the |
I have had this problem come up in a variety of places and 99% of the time the problem was that I was waiting for synchronous code. That is why for some people the error went away, after they removed an The following three examples are the ways I tend to use await the most, maybe it helps clean up tests:
it('renders the send button', async () => {
const utils = renderConversation();
await waitFor(() => {
expect(utils.getByText('Send')).toBeTruthy();
});
});
it('hides the reply', async () => {
const utils = renderConversationScreen();
const replyButton = await utils.findByTestId('hideReplyButton'); // findBy... uses waitFor internally i.e. await waitFor(() => utils.getByTestId('hideReplyButton')); workes just as well
fireEvent(reply, 'Press');
expect(utils.queryByTestId('hideReplyButton')).toBeNull();
});
it('queues the conversation messages', async () => {
const utils = renderConversationScreen();
const conversation = utils.getByTestId('Conversation');
const reply = await utils.findByTestId('replyView'); // I wait for the reply to render
const replyNativeEvent = { layout: { height: 50 } };
await act(async () => {
fireEvent(reply, 'Layout', { nativeEvent: replyNativeEvent }); // I wait for all animations to complete
});
expect(conversation.props.queueMessages).toBe(true);
}); @KieranO547 though the code is quite simple two follow up question:
|
@ccfz Thanks for all the info. So to answer the 2 follow up Q's,
The touched state is only used for the disabled state of the login button which is exactly what I'm testing for. Edit: I just refactored the test to be:
Matching your third suggestion and thats got rid of the messages for me so i suppose thats what i was missing, the error message handler is part of the component library - still getting used to testing lol - And thank you again! |
I'm also getting this console error logged when I call findByText more than once. I'm trying to understand if this is an actual bug as @renanmav and @milesj seem to suggest or if I'm just not understanding something about how to use react testing library properly. A simple (albeit contrived) example I've come up with to reproduce the bug is: function Example() {
const [value, setValue] = useState(0);
const asyncIncrement = useCallback(() => {
setTimeout(() => setValue((v) => v + 1), 0);
}, []);
return (
<View>
<Text>Value is {value}</Text>
<Button title="Increment" onPress={asyncIncrement} />
</View>
);
};
test("it works", async () => {
const { findByText, getByRole, getByText } = render(<Example />);
getByText(/Value is 0/i);
const button = getByRole("button");
fireEvent.press(button);
await findByText(/Value is 1/i);
//If I comment out these lines and no error message will be logged
fireEvent.press(button);
await findByText(/Value is 2/i);
// If I uncomment these lines and two error messages will be logged
// fireEvent.press(button);
// await findByText(/Value is 3/i);
}); The test seems to work fine (passes as written, fails if I change expected values), but I still see the console.error message warning complaining that "You called act(async () => ...) without await" I get this console error once per additional time (beyond the first) that I make calls to Could someone help me understand what is going wrong here? |
@ryanhobsonsmith By default Maybe try:
|
Thanks @milesj, your notes about the act handler make sense. Unfortunately, your proposed solution didn't work. My understanding is that waitFor just periodically executes the interior function until it returns a value and, as you said, the Just in case const asyncIncrement = useCallback(() => {
return new Promise((resolve) => {
setTimeout(() => {
setValue((v) => v + 1);
resolve();
}, 0);
});
}, []); Feels unnecessary, but with or without that above event handler change I'm still seeing that same error. Again, reason I'm leaning towards this being a bug is that the test actually seems to pass and work as expected outside of this console error message. I.e. subsequent findByText calls properly wait for the value to appear like I'd expect, just getting the console spam that makes me feel like I must be doing something wrong or may have a subtle bug somewhere. |
Ok, Miles' solution was too far off actually. In case anyone else finds this and wants a quick workaround inspired by: facebook/react#15416, then the best hack I've got work around things is to just build in some manual wait time inside of an function wait(ms) {
return act(() => new Promise((r) => setTimeout(r, ms)));
}
function fireAsyncPress(element, ms = 1) {
fireEvent.press(element);
return wait(ms);
}
test("it works", async () => {
const { getByRole, getByText } = render(<Example />);
getByText(/Value is 0/i);
const button = getByRole("button");
await fireAsyncPress(button);
getByText(/Value is 1/i);
await fireAsyncPress(button);
getByText(/Value is 2/i);
}); Would be happy to learn if anyone has worked out a better practice for testing these kinds of async event handlers / state-changes though. |
Yeah, I actually do something similar, like this. async function waitForEvent(cb) {
await waitFor(async () => {
await cb();
await new Promise(resolve => process.nextTick(resolve));
});
}
await waitForEvent(() => fireEvent.press()); |
@ryanhobsonsmith I played around with your examples and I would agree that there is a bug. My workaround is:
@ryanhobsonsmith could you maybe create a PR with your example from above
because that should have definitely worked and the issue does not actually have a PR yet. |
@Norfeldt Here is your quick hack: Create const nodePromise = Promise;
module.exports = (r) => nodePromise.resolve().then(r); Then add to moduleNameMapper: {
'^asap$': '<rootDir>/test/asap.js',
'^asap/raw$': '<rootDir>/test/asap.js'
}, |
thanks a lot @mmomtchev you just improved my testing experience ❤️ |
It seems that in the past the I've noticed that this is no longer the case as our Could anyone of interested people here, verify/reject that on your own code/tests, without any additional workarounds? @Norfeldt, @mmomtchev, @tcank, @jpmonette |
@mdjastrzebski I removed my patch, upgraded RNTL 9 - did some fixes for some test and it works without the act warnings (it does however seems a little flaky, since sometimes I did see some, but when I ran the test again they were gone 😳). Upgraded to 11 and it also seems to work. |
@Norfeldt awesome, thanks for feedback! |
@mdjastrzebski a few hours after my reply I can conclude that the it has changed from always happing to sometimes happing (flaky) 😢 |
@Norfeldt can you explain what exactly do you mean by flacky here? |
Meaning that when I run all my tests the warning appears in some of them. But then I run them again and they then disappear or occur in some other tests. |
@Norfeldt Hmmm, so it get's non-deterministic. Not sure if that better or worse. Does your previous patch resolve the flakyness? |
I have re-enabled
the warnings are gone.. 🙈 |
@Norfeldt: |
Yes, it works like magic 🪄 |
The longest running RNTL issue :-) @Norfeldt @mmomtchev @kuciax @tcank @rijk @ccfz @ryanhobsonsmith @milesj @seyaobey-dev did anyone here actually observed incorrect testing behaviour occurring as a result of this warning or is it just annoying because we like not to have warnings in our test runs? |
I'm experiencing flaky CI tests (when I rerun them they pass, they always pass on my local machine). It could be a server issue, but it always lures in the back of my mind if it's related to this issue. The annoyance is a small thing. When running over 100 tests or debugging one, it makes it hard to work in the terminal. https://youtu.be/xCcZJ7bQFQA look at 59:04 to see an example of what I mean. |
Yes it does actually cause problems, with the biggest issue being that it masks failures and tests pass with a green status. This is primarily caused by RN overriding the global Once we patched that, it uncovered a massive amount of these problems. Invalid mocks, accessing on undefined errors, so on and so forth. I really have no idea why this happens, but it does. |
I think that the only solution which addresses the root cause is to remove |
@Norfeldt @milesj @mmomtchev do you use RNTL Jest preset: |
Never tried. I don't work on the RN stuff anymore. |
I'm trying to upgrade to jest 28 as part of my expo upgrade to SDK 46, but it's giving me some trouble. software-mansion/react-native-reanimated#3215 Will report back if anything has change regarding this - once I resolve the blockers. |
@Norfeldt that's weird our recent basic example uses Expo 46 & Jest 28: https://github.com/callstack/react-native-testing-library/blob/main/examples/basic/package.json |
This basic example does not have reanimated. |
@mdjastrzebski I'm now on jest 29 and expo SDK 46 and the issue is still there. I believe that it's because I want to render components that uses react-query and fetches data from a local or staging server. I have a way to (almost) know when it's done, but checking that the queryClient has saved/cached the data. Is there some way to render with an act - so I can tell RNTL that state updates should be done? Currently I'm using export function renderWithProviders(ui: React.ReactElement) {
const testQueryClient = createTestReactQueryClient()
const AppProviders = ({ ui }: { ui: React.ReactElement }) => (
<QueryClientProvider client={testQueryClient}>
<CurrentPortfolioContextProvider>{ui}</CurrentPortfolioContextProvider>
</QueryClientProvider>
)
const { rerender, ...result } = ReactNativeTestingLibrary.render(<AppProviders ui={ui} />)
return {
utils: {
...result,
testQueryClient,
rerender: (rerenderUi: React.ReactElement) => rerender(<AppProviders ui={rerenderUi} />),
},
}
}
export async function renderWithUserAsync(
Element: JSX.Element,
email: keyof typeof userCredentials,
awaitQueryClient?: boolean
) {
await api.signOut() // allows avoiding to clean-up session after each test
const user = await getSessionAsync(email)
const { utils } = renderWithProviders(Element)
if (awaitQueryClient === undefined || awaitQueryClient) {
expect(utils.testQueryClient.getQueryData(QUERY_KEY_USER)).toBeUndefined()
await waitFor(() => {
expect(utils.testQueryClient.getQueryData(QUERY_KEY_USER)).toEqual(user)
})
}
return { utils, user }
} |
@Norfeldt @milesj @mmomtchev we've released RNTL v11.2.0 that might have improved this issue. Could you verify if it improves things for you. Pls report your React, React Test Renderer & React Native version along with feedback info so we can get a better understanding of the issue. |
console.error
Warning: An update to _default inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at _default
at CurrentPortfolioContextProvider
at QueryClientProvider
at AppProviders As mentioned, I think it's because I do some fetching and react-query does some react state management. If I just had a way to render it and tell it when RQ is done with the state management, then that might solve it. |
This did fix this error for me. I am in a new codebase that I set up last week. I was on React Native Testing Library 11.1.0, and upgrading to 11.2.0 made it so the warning I was receiving went away. Here was the test where I had been getting the warning, though the test itself was passing. I presume I was getting the warning because I had multiple const mocks = [signInMutationMock(...)]
renderApplication({ mocks })
expect(await screen.findByTestId('SignInScreen')).toBeDefined()
fireEvent.changeText(
screen.getByLabelText('Email address'),
'[email protected]',
)
fireEvent.changeText(screen.getByLabelText('Password'), 'Password')
fireEvent.press(screen.getByRole('button'))
// this was the line that was triggering the console error
expect(await screen.findByTestId('HomeScreen')).toBeDefined()
fireEvent.press(screen.getByText('Sign Out'))
await pressAlertButton('Yes, sign out')
expect(screen.queryByTestId('HomeScreen')).toBeNull()
expect(screen.getByTestId('SignInScreen')).toBeDefined() React: 18.1.0 |
Closing this issue, as it became non-actionable. Recent changes seem to have fixed the warnings for some users, while this issue contains various instances of If you will find @jpmonette, @Norfeldt, @mmomtchev, @tcank, @rijk ^ |
i am having this problem with |
Ask your Question
I have a simple test that seems to generate the right snapshot at the end of execution, but throws me a
console.error
during the execution.Here are the steps to get to the expected component state:
useEffect
asynchronous logic to be executed so that theSegmentedControlIOS
is exposed (testID
=recordTypePicker
)selectedSegmentIndex
to"1"
newSObjectLayout
testID
The test
The console log
The text was updated successfully, but these errors were encountered: