skip to content

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%

answer

  1. priority: role, input queries, accessible, testID
  2. no textbox role for TextInput
  3. ByLabelText reads accessibilityLabel or aria-label
  4. labelledby resolves a nativeID
  5. ByTestId is the last resort

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.

solid answer

~40 s

React Native Testing Library's priority is: `*ByRole` first, then the **text input queries**, then other accessible queries (`*ByText`, `*ByLabelText`, `*ByHintText`), and `*ByTestId` last. Inputs get their own tier because React Native has no general text-field role: `searchbox` exists but only suits search fields, so `getByRole` cannot reach an email field. Within that tier I prefer `getByLabelText('Email')`, which matches `aria-label` or `accessibilityLabel`, or the text of an element whose `nativeID` is referenced by `aria-labelledby` or `accessibilityLabelledBy`; it is what a screen reader announces. `getByPlaceholderText` and `getByDisplayValue` (which matches `value` or `defaultValue`) only match `TextInput`s and are weaker, because a placeholder is not a label and a value changes. A test ID is acceptable only when nothing user-facing identifies the field.

code

tsx · 20 lines
tsx
import { Text, TextInput, View } from 'react-native';
import { render, screen } from '@testing-library/react-native';

function CredentialsFields() {
  return (
    <View>
      <Text nativeID="emailLabel">Email</Text>
      <TextInput aria-labelledby="emailLabel" placeholder="[email protected]" />
      <TextInput accessibilityLabel="Password" secureTextEntry />
    </View>
  );
}

test('finds inputs by their labels', async () => {
  await render(<CredentialsFields />);

  expect(screen.getByLabelText('Email')).toBeOnTheScreen(); // via nativeID
  expect(screen.getByLabelText('Password')).toBeOnTheScreen();
  expect(screen.getByPlaceholderText('[email protected]')).toBeOnTheScreen(); // weaker
});

go deeper

for a junior

Recall the order: role first, then label, placeholder and display value for inputs, then text and hint, and test ID last.

for a middle

Explain why TextInput has its own tier, what getByLabelText resolves, including nativeID references, and why placeholder and display value are weaker.

for a senior

Use label-based queries to catch unlabelled fields, review suites for test-ID overuse, and reserve test IDs for elements without a user-facing identity.

for a principal

Define how the design system exposes labels and roles so that component tests, screen-reader behaviour and end-to-end locators stay consistent across the app.

## The priority RNTL recommends React Native Testing Library documents a four-tier order for choosing a query **predicate**: 1. **`*ByRole`**, narrowed with `name` and state options; 2. **text input queries**: `*ByLabelText`, `*ByPlaceholderText`, `*ByDisplayValue`; 3. **other accessible queries**: `*ByText`, `*ByLabelText`, `*ByHintText`; 4. **`*ByTestId`**, as the final resort. Tier 2 is specific to React Native. On the web a text input has an implicit role that a role query can reach. A React Native `TextInput` has none: RNTL reports its role as `none` unless one is set, and the only fitting role, `searchbox` (alias `search`), is meant for search fields. So the email and password fields of a login form cannot be found with `getByRole` unless the app misuses a role. ## The text input queries compared | Query | Matches | Elements | Strength | |---|---|---|---| | `getByLabelText('Email')` | `aria-label`, `accessibilityLabel`, text of the element referenced by `aria-labelledby` / `accessibilityLabelledBy`, `Image` `alt` | any host element | What assistive technology announces; also verifies the field is labelled | | `getByPlaceholderText('[email protected]')` | `placeholder` | `TextInput` only | Visible hint, but not a label and gone once the user types | | `getByDisplayValue('[email protected]')` | `value` or `defaultValue` | `TextInput` only | Useful to assert what is filled in; fragile as a locator | The labelled-by form suits forms whose visible label is a separate `Text`: give that `Text` a `nativeID` such as `emailLabel` and the input `aria-labelledby="emailLabel"`. RNTL resolves the reference and matches the label's text. React Native's documentation marks `aria-labelledby` and `accessibilityLabelledBy` as Android props, but RNTL resolves them in tests regardless of platform; when `accessibilityLabelledBy` holds an array of IDs, their texts are joined with spaces. ## Tier 3 and 4 on the same screen - `getByText('Forgot password?')` finds a link-like `Text`; if it is a real control, prefer `getByRole('link', { name: 'Forgot password?' })`. - `getByHintText('Signs in with your saved account')` matches `accessibilityHint`; hints are secondary descriptions, so this is rarely the primary locator. The same query is also exported as `getByA11yHint` and `getByAccessibilityHint`. - `getByTestId('login-email')` matches the `testID` prop. Users never see a test ID, so a test built on it keeps passing when the label disappears. Test IDs are common in device-level end-to-end tools, which cannot always reach elements by accessibility; in component tests they are the exception. ## A login form test in this order 1. Find the fields with `getByLabelText('Email')` and `getByLabelText('Password')`. 2. Find the submit control with `getByRole('button', { name: 'Sign in' })`. 3. After interaction, find the error with `getByRole('alert', { name: /incorrect password/i })`. 4. Reach for `getByTestId` only for elements with no user-facing identity, such as a decorative container. ## Why the order matters - **Resilience**: labels and roles change when the product changes, not when a developer refactors markup. - **Accessibility as a side effect**: a field that cannot be found by label is also unlabelled for screen-reader users, so the test fails for a real reason. - **Readable failures**: `Unable to find an element with accessibility label: Email` points straight at the defect.

  • When is getByDisplayValue the right query?
    When the assertion is about the value itself, for example that a remembered email is prefilled. It matches a `TextInput`'s `value` or `defaultValue`. As a locator it is fragile, because the value changes as the user types.
  • A designer removes the visible 'Email' label and keeps only the placeholder. What should the test reveal?
    If the test uses `getByLabelText('Email')` and the input had no separate accessibility label, it now fails, correctly: the field has lost its label for screen-reader users. A test using `getByPlaceholderText` or `getByTestId` would stay green and hide the regression.

saying these in an interview costs you the question

  • getByRole('textbox') finds any React Native TextInput.
  • getByPlaceholderText is as good as a label for locating fields.
  • getByLabelText only reads the accessibilityLabel prop.
  • Test IDs are the preferred locator in component tests because they are stable.
  • getByText can locate a TextInput by the text typed into it.