A React Native Testing Library test logs 'not wrapped in act(...)' after pressing a movie filter chip; what causes the warning and how do you fix it?
answer
- RNTL already wraps its calls in act
- a missing await is cause number one
- updates landing after the last await
- await the visible outcome instead
- never silence it with manual act
basics
~20 sA React state update ran outside act: usually an un-awaited user.press or fireEvent call, or async work it started, such as a fetch or debounce timer, settling after the test's last await. Await every event, then await the visible outcome.
solid answer
~50 sReact Native Testing Library already runs `render`, `fireEvent` and every `userEvent` dispatch inside `act`, and `waitFor` suspends the act check while it waits, so the message `An update to ... inside a test was not wrapped in act(...)` means some update escaped those calls. In interaction tests there are three usual causes. First, a missing `await` on `user.press(chip)` or `fireEvent.press`, so the events run after the test has moved on. Second, the handler starts asynchronous work, such as a mocked fetch or a debounced search timer, that resolves after the last awaited call; the fix is to await the visible result with an async query rather than asserting immediately. Third, work still pending when the test ends and the tree unmounts. Wrapping RNTL calls in another `act` hides nothing and fixes nothing; in v14 a manual `act` is only for updates triggered outside RNTL, and it must be awaited.
code
tsx · 21 linesimport { render, screen, userEvent } from '@testing-library/react-native';
import { MovieBrowser } from './MovieBrowser';
import { fetchMovies } from './api';
jest.mock('./api');
const mockedFetch = jest.mocked(fetchMovies);
test('shows sci-fi movies after pressing the chip', async () => {
mockedFetch.mockResolvedValue([{ id: '1', title: 'Arrival' }]);
await render(<MovieBrowser />);
const user = userEvent.setup();
await user.press(screen.getByRole('button', { name: 'Sci-Fi' }));
// Wrong: asserting immediately lets the fetch resolve outside act.
// expect(screen.getByText('Arrival')).toBeOnTheScreen();
// Right: wait for the outcome the user waits for.
expect(await screen.findByText('Arrival')).toBeOnTheScreen();
expect(mockedFetch).toHaveBeenCalledWith('sci-fi');
});go deeper
Recall that RNTL calls are already wrapped in act, and that forgetting await on an event is the first thing to check.
Explain what act guarantees, which RNTL APIs use it internally, and why async work started by a handler escapes the awaited event call.
Diagnose the escaping update, fix the test by awaiting the visible outcome or cancelling pending work, and reject redundant act wrapping or silenced logs.
Treat act warnings as defects in CI, for example by failing tests on them, so racing tests are fixed before they become flaky.
## What the warning says React prints `An update to <Component> inside a test was not wrapped in act(...)` when a state update happens in a test environment outside an `act` scope. `act` groups renders, effects and state updates so that, by the time it resolves, the tree looks as it would in the app. An update outside it may never be rendered before the assertion, or may land after the test finished. React Native Testing Library covers its own entry points: - `render`, `rerender` and `unmount` run inside async `act`; - `fireEvent` and its helpers run the handler inside `act`; - every event that `userEvent` dispatches runs inside `act`; - `waitFor` and the `findBy*` queries switch off React's act-environment check while they wait, so updates that land during the wait do not warn. So the warning in an interaction test almost always points to an update that **escaped** those calls. ## The usual causes in interaction tests | Cause | Typical code | Fix | |---|---|---| | Event call not awaited | `user.press(chip);` | `await user.press(chip);` | | Handler starts async work that settles later | the chip's `onPress` calls a mocked fetch; the test asserts right after the press | await the resulting UI with an async `findBy*` query | | Debounced search | `onChangeText` sets a timer that updates results later | await the result; with fake timers, advance them through the RNTL setup | | Work pending at the end of the test | a request resolves after the last assertion | await the final UI state, or cancel the work in the effect cleanup | ## Walking through the movie-filter case 1. The test presses the **Sci-Fi** chip: `await user.press(chip)`. 2. `onPress` sets `loading` and calls `fetchMovies('sci-fi')`, a mocked promise. 3. `user.press` resolves once its events and their synchronous updates are flushed. The fetch has not resolved yet. 4. The test asserts and ends. The fetch then resolves and calls `setMovies`, which is **outside any act**: warning. The fix is to wait for what the user would wait for: - `expect(await screen.findByText('Arrival')).toBeOnTheScreen()` keeps the test waiting, with the act check suspended, until the list shows the result, so the late `setMovies` no longer warns; - the rest of the assertions then run against the settled screen. ## Fixes that are wrong - **Wrapping RNTL calls in `act`**: `await act(async () => { await user.press(chip); })` is redundant, because the calls are already wrapped, and it does not cover the later fetch. - **Silencing `console.error`**: the warning disappears, the race stays, and the test becomes flaky. - **Adding sleeps**: a fixed delay is slower and still races on a loaded machine. ## When manual act is right In v14 `act` is async and always returns a Promise. A manual `await act(() => { ... })` is appropriate only for updates triggered **outside** RNTL's APIs, such as calling a store's setter or emitting from a mocked event emitter directly in the test. ## Why it matters beyond noise An act warning is a **race report**. Left alone it becomes a flaky test: on a fast machine the update happens to land before the assertion, on a slow CI runner it does not. It can also leak into the next test when an update fires after unmount. Treating every warning as a defect keeps interaction tests deterministic.
- Why can an act warning appear in a test that passes?Because the assertion ran against the screen before the late update, and happened to match. The update then landed outside act, possibly after the test ended. The test is racing, and on a slower machine the same code can fail or leak an update into the next test.
- When is a manual await act(...) justified in RNTL 14?When the test itself triggers a React update outside RNTL's APIs, for example calling a store's setter or emitting from a mocked event emitter. `act` is async in v14, so it must be awaited. It is never needed around `render`, `fireEvent` or `userEvent` calls.
saying these in an interview costs you the question
- Every userEvent call should be wrapped in act() by hand.
- Act warnings are harmless noise that can be silenced.
- Awaiting user.press also waits for any fetch the handler started.
- A fixed sleep after the press is a proper fix.
- act is synchronous in RNTL 14, so it need not be awaited.