In React Native Testing Library 14, how do you give every rendered survey screen its providers, and what does the render wrapper option do that inline wrapping does not?
answer
- wrapper takes a component type
- applied on render and every rerender
- custom render helper must be async
- return await render inside it
- list the helper for the codemod
basics
~10 sPass providers through render's wrapper option, a component that receives the tested element as children, or build an async renderWithProviders helper around it. RNTL re-applies the wrapper on every rerender, so providers survive updates.
solid answer
~40 sScreens that read context, such as the survey's `SurveyProvider` and a theme provider, crash or render wrongly if rendered bare. `render(ui, { wrapper: Providers })` takes a component type; RNTL renders `<Providers>{ui}</Providers>` and, importantly, wraps again on every `rerender(...)`, so updating props later does not strip the providers the way calling `rerender(<SurveyScreen />)` after an inline `render(<Providers><SurveyScreen /></Providers>)` would. For a whole suite, put that in a helper: `export async function renderWithProviders(ui, options) { return await render(ui, { wrapper: makeProviders(options), ...rest }); }`. In v14 the helper must be `async` and return the awaited `render`, and its call sites need `await`. When migrating, pass its name to the `rntl-v14-async-functions` codemod's `customRenderFunctions` parameter so call sites are updated.
code
typescript · 12 linesimport { screen } from '@testing-library/react-native';
import { renderWithProviders } from '../test-utils';
test('switching surveys keeps providers', async () => {
const { rerender } = await renderWithProviders(<SurveyScreen surveyId="s1" />, {
survey: onboardingSurvey,
});
expect(screen.getByRole('header', { name: 'Onboarding survey' })).toBeOnTheScreen();
await rerender(<SurveyScreen surveyId="s2" />);
expect(screen.getByRole('button', { name: 'Submit' })).toBeDisabled();
});go deeper
Recall that render accepts a wrapper component for providers, and that a helper built on it must be awaited in RNTL 14.
Explain why the wrapper survives rerender while inline wrapping does not, and how an async renderWithProviders helper is structured.
Design a test-utils layer with per-test provider options, reset global state between tests, and migrate helpers with customRenderFunctions.
Decide how much of the app shell tests should mount by default, balancing realism against speed and clarity about each screen's dependencies.
## The problem a wrapper solves A survey screen in a React Native app rarely renders on its own. It reads the current survey from a `SurveyProvider`, colours from a theme context, and perhaps a query client or a store. Rendering `<SurveyScreen />` bare in a test either throws ("useSurvey must be used within SurveyProvider") or renders a default, meaningless state. **React Native Testing Library (RNTL)** offers two ways to supply providers: wrap the element yourself, or pass a `wrapper` option to `render`. Both render the same tree the first time. They differ on every update after that. ## The `wrapper` option ```tsx await render(<SurveyScreen surveyId="s1" />, { wrapper: AppProviders }); ``` `wrapper` is a **component type**, not an element. RNTL renders `<AppProviders><SurveyScreen surveyId="s1" /></AppProviders>`. It keeps the wrapper and applies it again whenever the test calls `rerender`: ```tsx const { rerender } = await render(<SurveyScreen surveyId="s1" />, { wrapper: AppProviders }); await rerender(<SurveyScreen surveyId="s2" />); // still inside AppProviders ``` With inline wrapping, `rerender` receives only what you pass it. Writing `await rerender(<SurveyScreen surveyId="s2" />)` after an inline-wrapped `render` replaces the whole tree, so the providers disappear and the screen loses its context. You would have to repeat the providers on every `rerender`. ## A custom render function Most suites wrap this in one helper so tests stay short and consistent: 1. Define a providers component that accepts per-test options (initial survey, theme, logged-in user). 2. Create an `async` function that calls `render` with `wrapper` set. 3. `return await render(...)` so callers receive the same result as a normal `render`. 4. Export it from a test-utils module and use it instead of `render`. ```tsx export async function renderWithProviders( ui: React.ReactElement, { survey = sampleSurvey, theme = 'light' }: ProviderOptions = {}, ) { function Providers({ children }: { children: React.ReactNode }) { return ( <ThemeProvider value={theme}> <SurveyProvider initialSurvey={survey}>{children}</SurveyProvider> </ThemeProvider> ); } return await render(ui, { wrapper: Providers }); } ``` Since RNTL 14, `render` returns a Promise; a helper that forgets `async`/`await` hands tests an unresolved promise, and queries run before the screen mounts. ## Choosing between the approaches | Approach | Providers kept on `rerender` | Per-test options | Best for | |---|---|---|---| | Inline JSX in the test | no, unless repeated | yes, by editing JSX | one-off tests | | `wrapper` option | yes | via a closure or factory | tests that rerender | | Custom render helper using `wrapper` | yes | yes, as function arguments | whole suites | ## Several helpers, not one The RNTL cookbook suggests more than one custom render function when tests differ in scope: for example a `renderScreen` that mounts a single survey screen with its data providers, and a `renderNavigator` that mounts the navigation stack for flow tests such as "answer three questions, then see the summary". Each keeps its own wrapper, so a screen test does not pay for a navigator it does not use. The helpers can also accept a starting state, such as a half-completed survey, which keeps the setup of each test visible at the call site rather than hidden in a shared `beforeEach`. ## Migration notes - The `rntl-v14-async-functions` codemod only updates calls to helpers you list with `--param customRenderFunctions="renderWithProviders"`; otherwise call sites keep a missing `await`. - `render` now warns on unknown options, so a v13 helper still forwarding `concurrentRoot` or `createNodeMock` shows up in the output. - Keep the providers component stable where possible. Defining `Providers` inside the helper, as above, is fine because it is created once per `render` call and reused for that test's rerenders. ## Pitfalls - Passing an element instead of a component: `wrapper: <AppProviders />` is not a component type. - Putting a whole navigation container and every global provider into every test, which slows the suite and hides what a screen really depends on; give the helper options for what varies. - Relying on providers with global singletons (a store created at module load) without resetting them between tests, which leaks state from one survey test into the next.
- Why must the wrapper be a component type rather than a ready-made element?RNTL renders `<Wrapper>{ui}</Wrapper>` itself, passing the tested element as `children`, both on the first render and on every rerender. An element has no slot for new children, so it cannot be re-applied around a different tree.
- Can a custom render helper do async setup before rendering?Yes. Because it is already `async` in v14, it can await test data or a store's hydration and then `return await render(...)` with providers built from that data. Callers just `await` the helper as they would `render`.
saying these in an interview costs you the question
- Inline-wrapped providers stay in place when you call rerender with the bare screen.
- The wrapper option takes a provider element such as <AppProviders />.
- A custom render helper can stay synchronous in RNTL 14.
- The v14 codemod finds and fixes every custom render helper automatically.
- Every test should render the full navigation container and all providers.