In React Native Testing Library 14, why do queries return host elements like View and Text instead of your own composite components?
answer
- Test Renderer exposes host elements only
- composite View renders a host View
- Pressable renders an accessible host View
- type is a string such as 'Text'
- props asserted are what reaches native
basics
~20 sRNTL 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.
solid answer
~40 sReact Native distinguishes **host components**, which have a counterpart in the native view tree (`View`, `Text`, `TextInput`, `Image`, or a library's native view), from **composite components**, which exist only in JavaScript: your `LoginButton`, and even the `View` you import, which is a composite that renders a host `View`. Since v14, React Native Testing Library renders with Test Renderer, whose tree contains only host elements, so every query returns a `TestInstance` whose `type` is a string like `"Text"`. `Pressable` has no host of its own; it renders a host `View` with `accessible` set. This is deliberate: a test sees what the user could see, and a prop a composite forgets to forward, say `accessibilityLabel`, is missing in the test just as it is missing on the device.
code
tsx · 22 linesimport { Pressable, Text } from 'react-native';
import { render, screen } from '@testing-library/react-native';
type Props = { title: string; onPress: () => void; accessibilityLabel?: string };
// Bug: accessibilityLabel is accepted but never forwarded.
function LoginButton({ title, onPress }: Props) {
return (
<Pressable role="button" onPress={onPress}>
<Text>{title}</Text>
</Pressable>
);
}
test('the label must reach the host view', async () => {
await render(
<LoginButton title="Sign in" accessibilityLabel="Sign in to your account" onPress={jest.fn()} />,
);
expect(screen.getByText('Sign in').type).toBe('Text'); // a host element
expect(screen.queryByLabelText('Sign in to your account')).not.toBeOnTheScreen(); // exposes the bug
});go deeper
Recall that queries return host elements such as View, Text and TextInput, never your own components.
Explain the composite and host split, why the imported View is a composite, and what Pressable renders underneath.
Use the host-only tree to catch props that are not forwarded, migrate type-based queries, and know which native behaviours component tests cannot cover.
Decide which guarantees belong in component tests versus device-level tests, given that the test environment simulates rather than runs the native layer.
## Two kinds of components React Native Testing Library's element tree only makes sense once two categories are separated: - **Host components** have a direct counterpart in the native view hierarchy. `View`, `Text`, `TextInput` and `Image` render native views; libraries can add their own native views. - **Composite components** are units of JavaScript code: every function component you write, and also the components imported from `react-native` that merely wrap a host. That is why `View` appears in both lists. The `View` you import is a **composite** that processes its props and renders a **host** `View` as its only child. `Text`, `TextInput` and `Image` follow the same pattern. `Pressable` and `TouchableOpacity` have no host of their own: the composite renders a host `View` with props such as `accessible` already set. A full React tree for a login button looks like this: ```text <LoginButton> composite (yours) <Pressable> composite (react-native) <View accessible> host <Text> composite <Text> host "Sign in" ``` ## What RNTL 14 shows you RNTL does not run React Native's renderer; it uses **Test Renderer**, which renders to JavaScript objects in Node. Since v14 that renderer exposes **only host elements**, so the tree above appears to a test as a host `View` containing a host `Text` with the string `Sign in`. Consequences: 1. **Every query returns a host element**, a `TestInstance` with a string `type` such as `"View"`, `"Text"` or `"TextInput"`, plus `props`, `parent` and `children`. 2. **Queries by component type are gone.** v13's `UNSAFE_getByType` and `UNSAFE_getByProps` could return composites; the host-only tree cannot, and v14 removed them. Standard queries replace them. 3. **Props are the ones that reached the host.** Asserting a prop checks what the native view would receive, after any processing by composites. ## Why that catches real bugs Consider a design-system button that accepts a label but never forwards it: | Component code | What a test sees | What a user gets | |---|---|---| | `LoginButton` forwards `accessibilityLabel` to its `Pressable` | `getByLabelText('Sign in')` finds the host `View` | Screen reader announces "Sign in" | | `LoginButton` drops `accessibilityLabel` | `getByLabelText('Sign in')` throws | Screen reader announces only the visible text, or nothing | | `LoginButton` drops `onPress` | The host `View` has no press handler to trigger | Tapping does nothing | A test that inspected the composite's own props would stay green in the second and third rows. The host-only tree makes the test fail for the same reason the app fails. ## Practical rules - Query by what the user perceives (role, label, text); never try to locate "the `LoginButton` component". - Avoid walking `parent` and `children` by hand: third-party components can change their host structure between versions. - `screen.root` is the first rendered host element and `screen.container` the pseudo-element holding everything, useful when a test must reach the root without a test ID. - Remember what the environment is **not**: no native code runs, native view state such as focus is simulated at best, and assertions do not touch a real native hierarchy. Device-level behaviour needs an end-to-end tool.
- How do you replace a v13 test that used UNSAFE_getAllByType(Button)?Query what the user perceives instead: `screen.getAllByRole('button')`, or by name when one button matters. The type-based query returned composites, which the v14 host-only tree no longer contains, so the replacement is also a better test.
- Why can't RNTL assert that a TextInput actually has keyboard focus?Because no native code runs. Focus is native view state, and Test Renderer only produces JavaScript objects for host elements. Behaviour that lives in the native layer, such as real focus or keyboard handling, needs a device-level end-to-end test.
Testing host elements is like inspecting a finished building rather than its blueprints: a wall that never got built is missing on the inspection, even if the blueprint shows it.
saying these in an interview costs you the question
- RNTL queries can return your own composite components.
- The View imported from react-native is itself the host element.
- RNTL runs the React Native renderer inside Jest.
- A prop passed to a composite always appears on the element the query returns.
- Pressable renders a native host view named Pressable.