Testing environment
This document is intended for a more advanced audience who want to understand the internals of our testing environment better, e.g., to contribute to the codebase. You should be able to write integration or component tests without reading this.
React Native Testing Library allows you to write integration and component tests for your React Native app or library. While the JSX code used in tests closely resembles your React Native app, things are not as simple as they might appear. This document will describe the key elements of our testing environment and highlight things to be aware of when writing more advanced tests or diagnosing issues.
React renderers
React allows you to write declarative code using JSX, write function or class components, or use hooks like useState. You need to use a renderer to output the results of your components. Every React app uses some renderer:
- React Native is a renderer for mobile apps,
- React DOM is a renderer for web apps,
- There are other more specialized renderers that can e.g., render to console or HTML canvas.
When you run your tests in the React Native Testing Library, somewhat contrary to what the name suggests, they are actually not using React Native renderer. This is because this renderer needs to be run on an iOS or Android operating system, so it would need to run on a device or simulator.
Test Renderer
Instead, RNTL uses Test Renderer, a modern, actively maintained renderer that allows rendering to pure JavaScript objects without access to mobile OS and can run in a Node.js environment using Jest (or any other JavaScript test runner). Test Renderer replaces the deprecated react-test-renderer package and provides better compatibility with React 19 and improved type safety.
Using Test Renderer has pros and cons.
Benefits:
- tests can run on most CIs (Linux, etc) and do not require a mobile device or emulator
- faster test execution
- light runtime environment
Disadvantages:
- Tests do not execute native code
- Tests are unaware of the view state that would be managed by native components, e.g., focus, unmanaged text boxes, etc.
- Assertions do not operate on native view hierarchy
- Runtime behaviors are simulated, sometimes imperfectly
It's worth noting that the React Testing Library (web one) works a bit differently. While RTL also runs in Jest, it has access to a simulated browser DOM environment from the jsdom package, which allows it to use a regular React DOM renderer. Unfortunately, there is no similar React Native runtime environment package. This is probably because while the browser environment is well-defined and highly standardized, the React Native environment constantly evolves in sync with the evolution of underlying OS-es. Maintaining such an environment would require duplicating countless React Native behaviors and keeping them in sync as React Native develops.
Element tree
Calling the render() function creates an element tree. This is done internally by invoking the createRoot() function from Test Renderer. The output tree represents your React Native component tree, containing only host elements. Each node of that tree corresponds to a host component that would have a counterpart in the native view hierarchy.
These tree elements are represented by HostElement type from Test Renderer:
For more details, see the Test Renderer documentation.
Host and composite components
To understand RNTL's element tree, it's important to know the difference between host and composite components in React Native:
- Host components have direct counterparts in the native view tree. Typical examples are
<View>,<Text>,<TextInput>, and<Image>from React Native. You can think of these as an analog of<div>,<span>etc on the Web. You can also create custom host views as native modules or import them from 3rd party libraries, like React Navigation or React Native Gesture Handler. - Composite components are React code organization units that exist only on the JavaScript side of your app. Typical examples are components you create (function and class components), components imported from React Native (
View,Text, etc.), or 3rd party packages.
That might initially sound confusing since we put React Native's View in both categories. There are two View components: composite and host. The relation between them is as follows:
- Composite
Viewis the type imported from thereact-nativepackage. It is a JavaScript component that renders the hostViewas its only child. - Host
View, which you do not render directly. React Native takes the props you pass to the compositeView, does some processing on them and passes them to the hostView.
In a full React tree, this would look like:
A similar relation exists between other composite and host pairs: e.g. Text, TextInput, and Image components.
Not all React Native components are organized this way, e.g., when you use Pressable (or TouchableOpacity), there is no host Pressable, but composite Pressable is rendering a host View with specific props being set:
Host-only element tree
In RNTL v14, Test Renderer only exposes host elements in the element tree. Composite components are not visible in the tree - you only see their host element output. This is an intentional design choice that aligns with Testing Library's philosophy: tests should focus on what users can see and interact with (host elements), not on implementation details (composite components).
For a HostElement, the type prop is always a string value representing the host component name, e.g., "View", "Text", "TextInput".
Tree nodes
RNTL v14 queries and the element tree only expose host elements. This aligns with Testing Library's philosophy: tests should assert on what users can see and interact with. Host elements represent the actual UI controls that users interact with, while composite components exist purely in the JavaScript domain.
Understanding props
When asserting props on host elements, you're verifying what actually reaches the native view. This is important because composite components may process, transform, or even forget to pass props to their host children.
In the above example, the component accepts onPress and style props but doesn't pass them to host views, so they won't affect the user interface. By testing host elements, RNTL helps you catch these issues: if a prop doesn't reach a host element, users won't see or interact with it.
Tree navigation
You should avoid navigating over the element tree, as this makes your testing code fragile and may result in false positives. This section is more relevant for people who want to contribute to our codebase.
You can navigate the tree of host elements using parent or children props of a HostElement. Be careful when doing this, as the tree structure for third-party components can change independently from your code and cause unexpected test failures.
Queries
All Testing Library queries return host components to encourage the best practices described above. Since v14, RNTL uses Test Renderer, which only renders host elements, making it impossible to query composite components directly.
