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 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:
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.
Instead, RNTL uses React Test Renderer, a specialized 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).
Using React Test Renderer has pros and cons.
Benefits:
Disadvantages:
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.
Calling the render()
function creates an element tree. This is done internally by invoking TestRenderer.create()
method. The output tree represents your React Native component tree, and each node of that tree is an "instance" of some React component (to be more precise, each node represents a React fiber, and only class components have instances, while function components store the hook state using fibers).
These tree elements are represented by ReactTestInstance
type:
Based on: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer/index.d.ts
One of the most important aspects of the element tree is that it is composed of both host and composite components:
<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.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:
View
is the type imported from the react-native
package. It is a JavaScript component that renders the host View
as its only child in the element tree.View
, which you do not render directly. React Native takes the props you pass to the composite View
, does some processing on them and passes them to the host View
.The part of the tree looks as follows:
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:
Any easy way to differentiate between host and composite elements is the type
prop of ReactTestInstance
:
"View"
You can use the following code to check if a given element is a host one:
We encourage you to only assert values on host views in your tests because they represent the user interface view and controls which the user can see and interact with. Users cannot see or interact with composite views as they exist purely in the JavaScript domain and do not generate any visible UI.
For example, suppose you assert a style
prop of a composite element. In that case, there is no guarantee that the style will be visible to the user, as the component author can forget to pass this prop to some underlying View
or other host component. Similarly onPress
event handler on a composite prop can be unreachable by the user.
In the above example, user-defined components accept both onPress
and style
props but do not pass them (through Pressable
) to host views, so they will not affect the user interface. Additionally, React Native and other libraries might pass some of the props under different names or transform their values between composite and host components.
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 will encounter host and composite elements when navigating a tree of react elements using parent
or children
props of a ReactTestInstance
element. You should be careful when navigating the element tree, as the tree structure for third-party components can change independently from your code and cause unexpected test failures.
Inside RNTL, we have various tree navigation helpers: getHostParent
, getHostChildren
, etc. These are intentionally not exported, as using them is not recommended.
All recommended Testing Library queries return host components to encourage the best practices described above.
Only UNSAFE_*ByType
and UNSAFE_*ByProps
queries can return both host and composite components depending on used predicates. They are marked as unsafe precisely because testing composite components makes your test more fragile.