skip to content

Screen Queries & Priority

RNTL queries find host elements by role, label, text or test ID through screen, in getBy, queryBy and findBy variants. Interviewers probe query priority and why getByTestId comes last.

on this pageshow

explore

questions

5

In React Native Testing Library 14, how do you render a component and find an element through screen, and why must render be awaited?

level: juniorimportance: must knowfreq 58%

answer

  1. render returns a Promise in v14
  2. screen points at the latest render
  3. getByText matches host Text only
  4. getBy throws on zero or several
  5. cleanup runs after each test

basics

~10 s

In RNTL 14 you write await render(<LoginScreen />), then query with screen.getByText('Welcome back'). render is async because it wraps rendering in an async act, and screen is only bound after that promise resolves.

solid answer

~40 s

React Native Testing Library 14 renders the component with Test Renderer into an in-memory tree of host elements; no simulator and no native code are involved. `render()` now returns a Promise because it runs the render inside an async `act`, which lets React 19 finish pending work such as `Suspense` and `use()`. Only when that promise resolves does the library bind `screen` to the new tree, so the test must be `async` and must `await render(...)`. Then `screen.getByText('Welcome back')` returns the single host `Text` whose content matches, and throws if there are none or several. `getAllBy*` returns a non-empty array and `queryBy*` returns `null` when nothing matches. After each test an automatic cleanup unmounts the tree and resets `screen`.

code

tsx · 9 lines
tsx
import { render, screen } from '@testing-library/react-native';
import { LoginScreen } from './LoginScreen';

test('shows the sign-in heading', async () => {
  await render(<LoginScreen />);

  expect(screen.getByText('Welcome back')).toBeOnTheScreen();
  expect(screen.queryByText('Session expired')).not.toBeOnTheScreen();
});

go deeper

for a junior

Recall the three steps: an async test, await render, then a screen query such as getByText, which throws when it finds nothing.

for a middle

Explain why render became async in v14, when screen is bound and reset, and the zero, one and many behaviour of each variant.

for a senior

Diagnose suites migrated from v13: missing awaits, destructured Promises and tests passing on unflushed trees, and apply the codemod with review.

for a principal

Set conventions for a large suite: screen as the only access style, lint rules for floating promises, and a pinned Test Renderer line matched to the React version.

## What render does in v14 React Native Testing Library (RNTL) does not run the React Native renderer, because that renderer needs iOS or Android. Since v14 it uses **Test Renderer**, the successor of the deprecated `react-test-renderer`, which renders components to plain JavaScript objects inside Jest on Node. The result is a tree of **host elements** (`View`, `Text`, `TextInput`, `Image` and so on) that mirrors what would reach the native view hierarchy. RNTL 14 requires React 19 and React Native 0.78 or later, and it adopted React 19's async rendering model: - `render()` returns `Promise<RenderResult>`; - `rerender()` and `unmount()` return `Promise<void>`; - the initial render runs inside an async `act`, so pending updates, `Suspense` boundaries and the `use()` hook settle before the promise resolves. ## screen versus the render result | Access style | Example | Notes | |---|---|---| | `screen` object | `screen.getByText('Welcome back')` | Bound to the most recent render; the recommended style | | Destructured result | `const { getByText } = await render(...)` | Same queries, tied to that render call | `screen` is assigned **after** the render promise resolves and is reset by the automatic cleanup that RNTL registers with `afterEach`. Before any render in a test, every `screen` method throws an error saying that `render` has not been called. ## Finding text with getByText - `*ByText` matches only **host `Text` elements**, by their text content. - Nested `Text` children are joined, so `<Text>Hello <Text>Ana</Text></Text>` matches `'Hello Ana'`, as React Native renders them together. - Matching is **exact by default**: whole string, case-sensitive. Pass a regular expression such as `/welcome/i` for fuzzy matching; `{ exact: false }` also works but regex is preferred. - Text is **normalized** first: leading and trailing whitespace trimmed, inner runs of whitespace collapsed. ## The variants in RNTL | Variant | Zero matches | One match | Several matches | |---|---|---|---| | `getBy*` | throws | returns it | throws | | `getAllBy*` | throws | array of one | array | | `queryBy*` | `null` | returns it | throws | | `queryAllBy*` | `[]` | array | array | Each returns a **`TestInstance`**, an object with a string `type`, `props`, `parent` and `children`. Waiting for elements that appear later is the job of the `findBy*` variants. ## What goes wrong without await 1. `render(<LoginScreen />); screen.getByText('Sign in')` throws because `screen` has not been bound yet. 2. `const { getByText } = render(...)` destructures a Promise, so `getByText` is `undefined` and the call fails with a type error. 3. A test that happens to pass may assert on a tree whose effects have not been flushed. The v14 migration provides a codemod that adds these `await`s, but new tests should be written async from the start.

  • When would you destructure queries from the render result instead of using screen?
    Rarely. `screen` always points at the latest render and keeps tests uniform. Destructuring from `await render(...)` is equivalent and can help when a helper returns the render result, but mixing both styles in one suite makes tests harder to read.
  • Why does getByText('Welcome') fail when the Text says 'Welcome back'?
    String matching is exact by default: the whole normalized string, case-sensitive. Use the full text, or a regular expression such as `/welcome/i` to match a substring regardless of case. `{ exact: false }` also works, but the docs prefer regex for fuzzy matching.

saying these in an interview costs you the question

  • render is still synchronous in RNTL 14, so await is optional.
  • RNTL renders the app on a simulator in the background.
  • getByText returns the first match when several elements match.
  • screen keeps the previous test's tree until the next render.
  • getByText can find text typed into a TextInput.
open as a page

In React Native Testing Library, what makes an element findable with getByRole, and how does the name option pick a login form's Sign in button?

level: middleimportance: must knowfreq 48%

basics

~20 s

getByRole matches only accessibility elements (Text, TextInput and Switch by default, or anything with accessible true, like the View a Pressable renders) whose role or accessibilityRole matches. The name option compares the accessible name: the label, else the text content.

open as a page

In React Native Testing Library 14, why do queries return host elements like View and Text instead of your own composite components?

level: middleimportance: should knowfreq 28%

basics

~20 s

RNTL 14 renders with Test Renderer, whose tree holds only host elements such as View, Text and TextInput, the ones with a native counterpart. Composite components live only in JavaScript, so queries return what would reach the native view.

open as a page

In React Native Testing Library, how should a test find a login form's email and password TextInputs, given TextInput has no default role?

level: middleimportance: should knowfreq 40%

basics

~10 s

TextInput has no default role, so after *ByRole the RNTL priority puts the text-input queries: getByLabelText first, matching accessibilityLabel, aria-label or a labelled-by nativeID, then getByPlaceholderText and getByDisplayValue. getByTestId stays the last resort.

open as a page

A React Native Testing Library query fails although screen.debug() prints the element; which accessibility rules can hide it, and what does includeHiddenElements change?

level: seniorimportance: should knowfreq 22%

basics

~20 s

RNTL queries skip elements hidden from accessibility by default: display none, aria-hidden, accessibilityElementsHidden, importantForAccessibility no-hide-descendants on the element or an ancestor, or a modal sibling. includeHiddenElements: true, or configure's defaultIncludeHiddenElements, makes queries match them anyway.

open as a page