callstack / react-native-testing-library

🦉 Simple and complete React Native testing utilities that encourage good testing practices.
https://callstack.github.io/react-native-testing-library/
MIT License
3.05k stars 271 forks source link

Error thrown - Warning: You called act(async () => ...) without await. #379

Closed jpmonette closed 1 year ago

jpmonette commented 4 years ago

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:

  1. Load the component
  2. Wait for the useEffect asynchronous logic to be executed so that the SegmentedControlIOS is exposed (testID = recordTypePicker)
  3. Set the selectedSegmentIndex to "1"
  4. Wait for the component to re-render and all the async logic to be executed
  5. Assert that the rendered component includes newSObjectLayout testID

The test

import * as React from 'react';
import { fireEvent, render, waitFor } 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 page layout exposed', async () => {
    const { container } = setup();

    await waitFor(() => expect(container.getByTestId('recordTypePicker')).toBeTruthy());

    const input = container.getByTestId('recordTypePicker');
    fireEvent(input, 'onChange', { nativeEvent: { selectedSegmentIndex: 1 } });

    await waitFor(() => expect(container.getByTestId('newSObjectLayout')).toBeTruthy());

    expect(container.toJSON()).toMatchSnapshot();
  });
});

The console log

./node_modules/.bin/jest src/containers/__tests__/NewSObject.spec.tsx --u
  console.error
    Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);

      at CustomConsole.console.error (node_modules/react-native/Libraries/YellowBox/YellowBox.js:63:9)
      at printWarning (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:120:30)
      at error (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:92:5)
      at node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14953:13
      at tryCallOne (node_modules/promise/lib/core.js:37:12)
      at node_modules/promise/lib/core.js:123:15
      at flush (node_modules/asap/raw.js:50:29)

 PASS  src/containers/__tests__/NewSObject.spec.tsx
  NewSObjectContainer
    ✓ should render a NewSObjectContainer - with page layout exposed (499ms)

 › 1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       q passed, q total
Snapshots:   1 written, 1 passed, q total
Time:        4.865s, estimated 8s
Ran all test suites matching /src\/containers\/__tests__\/NewSObject.spec.tsx/i.
mmomtchev commented 2 years ago

I think that the only solution which addresses the root cause is to remove asap from React Native. If there is a good reason for having it there, then the next best one is to make sure RNTL skips it in some way. Everything else is covering it up.

mdjastrzebski commented 2 years ago

@Norfeldt @milesj @mmomtchev do you use RNTL Jest preset: preset: '@testing-library/react-native',? Does using it make any difference regarding test runs?

milesj commented 2 years ago

Never tried. I don't work on the RN stuff anymore.

Norfeldt commented 2 years ago

I'm trying to upgrade to jest 28 as part of my expo upgrade to SDK 46, but it's giving me some trouble.

https://github.com/software-mansion/react-native-reanimated/issues/3215

Will report back if anything has change regarding this - once I resolve the blockers.

mdjastrzebski commented 2 years ago

@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

Norfeldt commented 2 years ago

@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.

Norfeldt commented 2 years ago

@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 .waitFor and not act

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 }
}
mdjastrzebski commented 1 year ago

@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.

Norfeldt commented 1 year ago
    "expo": "^46.0.10",
     ...
    "@testing-library/jest-native": "^4.0.12",
    "@testing-library/react-native": "^11.2.0",
    ...    
    "jest": "^29.0.3",
    "jest-expo": "^46.0.1",
    "msw": "^0.44.2",
    "patch-package": "^6.4.7",
    "prettier": "^2.5.1",
    "react-test-renderer": "^18.2.0",
    ...
    "ts-jest": "^29.0.1",
    "ts-node": "^10.9.1",
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.

stevehanson commented 1 year ago

we've released RNTL v11.2.0 that might have improved this issue. Could you verify if it improves things for you.

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 awaits in it:

const mocks = [signInMutationMock(...)]
renderApplication({ mocks })

expect(await screen.findByTestId('SignInScreen')).toBeDefined()

fireEvent.changeText(
  screen.getByLabelText('Email address'),
  'jan@example.com',
)
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 React Native: 0.70.1 RN Testing Library: 11.2.0 React Test Renderer: 18.1.0 (I don't have this added explicitly in my project, included by RN Testing Library)

mdjastrzebski commented 1 year ago

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 act warnings, both sync and async, triggered by different conditions that probably deserve separate treatment.

If you will find act warnings still occurring in the latest RNTL version, then please submit a GH issue with minimal repro repository based on ours examples/basic so that we have an actionable PR with clear repro case to fix.

@jpmonette, @Norfeldt, @mmomtchev, @tcank, @rijk ^

damdafayton commented 1 year ago

i am having this problem with @testing-library/react-native": "^11.5.0