rest of the docs

This commit is contained in:
Maciej Jastrzebski
2026-01-20 22:39:14 +01:00
parent 5a45abff2b
commit ac25e60883
15 changed files with 98 additions and 115 deletions
+6 -6
View File
@@ -14,11 +14,11 @@
## The problem
You want to write maintainable tests for your React Native components. As a part of this goal, you want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended. As part of this, you want your tests to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.
You want to write maintainable tests for your React Native components. Your tests should avoid implementation details and focus on giving you confidence. They should remain maintainable so refactors (changes to implementation but not functionality) don't break your tests and slow you and your team down.
## This solution
The React Native Testing Library (RNTL) is a comprehensive solution for testing React Native components. It provides React Native runtime simulation on top of `react-test-renderer`, in a way that encourages better testing practices. Its primary guiding principle is:
The React Native Testing Library (RNTL) tests React Native components. It simulates the React Native runtime on top of `react-test-renderer` and encourages better testing practices. Its primary guiding principle is:
> The more your tests resemble the way your software is used, the more confidence they can give you.
@@ -48,8 +48,8 @@ You can use the built-in Jest matchers automatically by having any import from `
import { render, screen, userEvent } from '@testing-library/react-native';
import { QuestionsBoard } from '../QuestionsBoard';
// It is recommended to use userEvent with fake timers
// Some events involve duration so your tests may take a long time to run.
// Use userEvent with fake timers
// Some events involve duration, so tests may take a long time to run.
jest.useFakeTimers();
test('form submits two answers', async () => {
@@ -87,8 +87,8 @@ React Native Testing Library consists of following APIs:
- Lifecycle methods: [`rerender`](https://callstack.github.io/react-native-testing-library/docs/api/screen#rerender), [`unmount`](https://callstack.github.io/react-native-testing-library/docs/api/screen#unmount)
- Helpers: [`debug`](https://callstack.github.io/react-native-testing-library/docs/api/screen#debug), [`toJSON`](https://callstack.github.io/react-native-testing-library/docs/api/screen#tojson), [`root`](https://callstack.github.io/react-native-testing-library/docs/api/screen#root)
- [Jest matchers](https://callstack.github.io/react-native-testing-library/docs/api/jest-matchers) - validate assumptions about your UI
- [User Event](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event) - simulate common user interactions like [`press`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#press) or [`type`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#type) in a realistic way
- [Fire Event](https://callstack.github.io/react-native-testing-library/docs/api/events/fire-event) - simulate any component event in a simplified way
- [User Event](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event) - simulate common user interactions like [`press`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#press) or [`type`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#type)
- [Fire Event](https://callstack.github.io/react-native-testing-library/docs/api/events/fire-event) - simulate any component event
- [`renderHook` function](https://callstack.github.io/react-native-testing-library/docs/api/misc/render-hook) - render hooks for testing purposes
- Miscellaneous APIs:
- [Async utils](https://callstack.github.io/react-native-testing-library/docs/api/misc/async): `findBy*` queries, `waitFor`, `waitForElementToBeRemoved`
+4 -18
View File
@@ -19,11 +19,11 @@
## The problem
You want to write maintainable tests for your React Native components. As a part of this goal, you want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended. As part of this, you want your tests to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.
You want to write maintainable tests for your React Native components. Your tests should avoid implementation details and focus on giving you confidence. They should remain maintainable so refactors (changes to implementation but not functionality) don't break your tests and slow you and your team down.
## This solution
The React Native Testing Library (RNTL) is a comprehensive solution for testing React Native components. It provides React Native runtime simulation on top of `test-renderer`, in a way that encourages better testing practices. Its primary guiding principle is:
The React Native Testing Library (RNTL) tests React Native components. It simulates the React Native runtime on top of [Test Renderer](https://github.com/mdjastrzebski/test-renderer) and encourages better testing practices. Its primary guiding principle is:
> The more your tests resemble the way your software is used, the more confidence they can give you.
@@ -34,20 +34,12 @@ This project is inspired by [React Testing Library](https://github.com/testing-l
Open a Terminal in your project's folder and run:
```sh
# Yarn install:
yarn add --dev @testing-library/react-native@alpha
# NPM install
npm install --save-dev @testing-library/react-native@alpha
```
This library has a `peerDependencies` listing for [Test Renderer](https://github.com/mdjastrzebski/test-renderer). Make sure to install it as a dev dependency:
```sh
# Yarn install:
yarn add --dev test-renderer
# NPM install
npm install --save-dev test-renderer
```
@@ -61,10 +53,6 @@ You can use the built-in Jest matchers automatically by having any import from `
import { render, screen, userEvent } from '@testing-library/react-native';
import { QuestionsBoard } from '../QuestionsBoard';
// It is recommended to use userEvent with fake timers
// Some events involve duration so your tests may take a long time to run.
jest.useFakeTimers();
test('form submits two answers', async () => {
const questions = ['q1', 'q2'];
const onSubmit = jest.fn();
@@ -100,8 +88,8 @@ React Native Testing Library consists of following APIs:
- Lifecycle methods: [`rerender`](https://callstack.github.io/react-native-testing-library/docs/api/screen#rerender), [`unmount`](https://callstack.github.io/react-native-testing-library/docs/api/screen#unmount)
- Helpers: [`debug`](https://callstack.github.io/react-native-testing-library/docs/api/screen#debug), [`toJSON`](https://callstack.github.io/react-native-testing-library/docs/api/screen#tojson), [`root`](https://callstack.github.io/react-native-testing-library/docs/api/screen#root), [`container`](https://callstack.github.io/react-native-testing-library/docs/api/screen#container)
- [Jest matchers](https://callstack.github.io/react-native-testing-library/docs/api/jest-matchers) - validate assumptions about your UI
- [User Event](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event) - simulate common user interactions like [`press`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#press) or [`type`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#type) in a realistic way
- [Fire Event](https://callstack.github.io/react-native-testing-library/docs/api/events/fire-event) - simulate any component event in a simplified way
- [User Event](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event) - simulate common user interactions like [`press`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#press) or [`type`](https://callstack.github.io/react-native-testing-library/docs/api/events/user-event#type)
- [Fire Event](https://callstack.github.io/react-native-testing-library/docs/api/events/fire-event) - simulate any component event
- [`renderHook` function](https://callstack.github.io/react-native-testing-library/docs/api/misc/render-hook) - render hooks for testing purposes
- Miscellaneous APIs:
- [Async utils](https://callstack.github.io/react-native-testing-library/docs/api/misc/async): `findBy*` queries, `waitFor`, `waitForElementToBeRemoved`
@@ -112,8 +100,6 @@ React Native Testing Library consists of following APIs:
## Migration Guides
- **[Migration to 14.0](https://callstack.github.io/react-native-testing-library/docs/migration/v14)** - Drops React 18, async APIs by default
- [Migration to 13.0](https://callstack.github.io/react-native-testing-library/docs/migration/v13)
- [Migration to built-in Jest Matchers](https://callstack.github.io/react-native-testing-library/docs/migration/jest-matchers)
## Troubleshooting
+12 -14
View File
@@ -6,7 +6,7 @@ This document is intended for a more advanced audience who want to understand th
:::
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 Native Testing Library lets you write integration and component tests for your React Native app or library. While the JSX code in tests closely resembles your React Native app, the underlying environment differs. This document describes the key elements of our testing environment and highlights things to be aware of when writing advanced tests or diagnosing issues.
## React renderers
@@ -20,21 +20,19 @@ When you run your tests in the React Native Testing Library, somewhat contrary t
## Test Renderer
Instead, RNTL uses [Test Renderer](https://github.com/mdjastrzebski/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.
Instead, RNTL uses [Test Renderer](https://github.com/mdjastrzebski/test-renderer), a modern, actively maintained renderer that renders to pure JavaScript objects without access to mobile OS and runs in a Node.js environment using Jest (or any other JavaScript test runner). Test Renderer replaces the deprecated `react-test-renderer` package and has better compatibility with React 19 and improved type safety.
Using Test Renderer has pros and cons.
Using Test Renderer has trade-offs:
Benefits:
- Tests run on most CIs (Linux, etc) without a mobile device or emulator
- Faster test execution
- Light runtime environment
- 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
Limitations:
- Tests don't execute native code
- Tests are unaware of view state managed by native components, e.g., focus, unmanaged text boxes, etc.
- Assertions don't 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.
@@ -90,13 +88,13 @@ Not all React Native components are organized this way, e.g., when you use `Pres
### Host-only element tree
In RNTL v14, [Test Renderer](https://github.com/mdjastrzebski/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).
In RNTL v14, [Test Renderer](https://github.com/mdjastrzebski/test-renderer) only exposes host elements in the element tree. Composite components aren't visible in the tree—you only see their host element output. This aligns with Testing Library's philosophy: tests should focus on what users can see and interact with (host elements), not 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.
RNTL v14 queries and the element tree only expose host elements. Tests assert on what users can see and interact with. Host elements represent the actual UI controls users interact with, while composite components exist purely in the JavaScript domain.
### Understanding props
@@ -1,6 +1,6 @@
# Understanding `act` function
When writing RNTL tests one of the things that confuses developers the most are cryptic [`act()`](https://react.dev/link/wrap-tests-with-act) function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of `act()` so you can build your tests with more confidence.
When writing RNTL tests, cryptic [`act()`](https://react.dev/link/wrap-tests-with-act) function errors logged to console often confuse developers. This article explains the purpose and behavior of `act()` so you can write tests with more confidence.
## `act` warning
@@ -28,7 +28,7 @@ This function is intended only for using in automated tests and works only in de
The responsibility for `act` function is to make React renders and updates work in tests in a similar way they work in real application by grouping and executing related units of interaction (e.g. renders, effects, etc) together.
To showcase that behaviour let make a small experiment. First we define a function component that uses `useEffect` hook in a trivial way.
Let's demonstrate this with a small experiment. First, define a function component that uses `useEffect`:
```jsx
function TestComponent() {
@@ -57,7 +57,7 @@ test('render without act', () => {
});
```
When testing without `act` call wrapping rendering call, we see that the assertion runs just after the rendering but before `useEffect` hooks effects are applied. Which is not what we expected in our tests.
When testing without `act` wrapping the render call, the assertion runs just after rendering but before `useEffect` effects are applied. This isn't what we expected.
```jsx
import { createRoot } from 'test-renderer';
@@ -99,7 +99,7 @@ Note that `act` calls can be safely nested and internally form a stack of calls.
The `act` implementation is defined in the [ReactAct.js source file](https://github.com/facebook/react/blob/main/packages/react/src/ReactAct.js) inside React repository. RNTL v14 requires React 19+, which provides the `act` function directly via `React.act`.
RNTL exports `act` for convenience of the users as defined in the [act.ts source file](https://github.com/callstack/react-native-testing-library/blob/main/src/act.ts). In v14, `act` is now async by default and always returns a Promise, making it compatible with async React features like `Suspense` boundary or `use()` hook. The underlying implementation wraps React's `act` function to ensure consistent async behavior.
RNTL exports `act` for convenience as defined in the [act.ts source file](https://github.com/callstack/react-native-testing-library/blob/main/src/act.ts). In v14, `act` is async by default and always returns a Promise. This works with async React features like `Suspense` boundaries and the `use()` hook. The underlying implementation wraps React's `act` function to ensure consistent async behavior.
**Important**: You should always use `act` exported from `@testing-library/react-native` rather than the one from `react`. The RNTL version automatically ensures async behavior, whereas using `React.act` directly could still trigger synchronous act behavior if used improperly, leading to subtle test issues.
@@ -111,7 +111,7 @@ In v14, `act` is always async and returns a Promise. While the callback you pass
When the callback passed to `act` contains asynchronous operations, the Promise returned by `act` will resolve only after those operations complete.
Lets look at a simple example with component using `setTimeout` call to simulate asynchronous behaviour:
Here's a simple example with a component using `setTimeout` to simulate asynchronous behavior:
```jsx
function TestAsyncComponent() {
@@ -139,7 +139,7 @@ If we test our component in a native way without handling its asynchronous behav
### Solution with fake timers
First solution is to use Jest's fake timers inside out tests:
Use Jest's fake timers:
```jsx
test('render with fake timers', async () => {
@@ -159,7 +159,7 @@ That way we can wrap `jest.runAllTimers()` call which triggers the `setTimeout`
### Solution with real timers
If we wanted to stick with real timers then things get a bit more complex. Let's start by applying a crude solution of opening async `act()` call for the expected duration of components updates:
With real timers, things get more complex. Start with a simple solution: wrap an async `act()` call for the expected duration of component updates:
```jsx
test('render with real timers - sleep', async () => {
@@ -174,7 +174,7 @@ test('render with real timers - sleep', async () => {
This works correctly as we use an explicit async `act()` call that resolves the console error. However, it relies on our knowledge of exact implementation details which is a bad practice.
Let's try more elegant solution using `waitFor` that will wait for our desired state:
A better solution uses `waitFor` to wait for the desired state:
```jsx
test('render with real timers - waitFor', async () => {
@@ -3,8 +3,7 @@
## `fireEvent` {#fire-event}
:::note
For common events like `press` or `type` it's recommended to use [User Event API](/docs/api/events/user-event) as it offers
more realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior.
For common events like `press` or `type`, use the [User Event API](/docs/api/events/user-event). It simulates events more realistically by emitting a sequence of events with proper event objects that mimic React Native runtime behavior.
Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.
:::
@@ -13,11 +12,11 @@ Use Fire Event for cases not supported by User Event and for triggering event ha
function fireEvent(element: HostElement, eventName: string, ...data: unknown[]): Promise<unknown>;
```
The `fireEvent` API allows you to trigger all kinds of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named `onXxx` when `xxx` is the name of the event passed.
The `fireEvent` API triggers event handlers on both host and composite components. It traverses the component tree bottom-up from the passed element to find an enabled event handler named `onXxx` where `xxx` is the event name.
Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object.
This function uses async `act` function internally to ensure all pending React updates are executed during event handling.
This function uses async `act` internally to execute all pending React updates during event handling.
```jsx
import { render, screen, fireEvent } from '@testing-library/react-native';
@@ -62,7 +61,7 @@ FireEvent exposes convenience methods for common events like: `press`, `changeTe
### `fireEvent.press` {#press}
:::note
It is recommended to use the User Event [`press()`](/docs/api/events/user-event#press) helper instead as it offers more realistic simulation of press interaction, including pressable support.
Use the User Event [`press()`](/docs/api/events/user-event#press) helper instead. It simulates press interactions more realistically, including pressable support.
:::
```tsx
@@ -101,7 +100,7 @@ expect(onPressMock).toHaveBeenCalledWith(eventData);
### `fireEvent.changeText` {#change-text}
:::note
It is recommended to use the User Event [`type()`](/docs/api/events/user-event#type) helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.
Use the User Event [`type()`](/docs/api/events/user-event#type) helper instead. It simulates text change interactions more realistically, including key-by-key typing, element focus, and other editing events.
:::
```tsx
@@ -132,7 +131,7 @@ await fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEX
### `fireEvent.scroll` {#scroll}
:::note
Prefer using [`user.scrollTo`](/docs/api/events/user-event#scrollto) over `fireEvent.scroll` for `ScrollView`, `FlatList`, and `SectionList` components. User Event provides a more realistic event simulation based on React Native runtime behavior.
Prefer [`user.scrollTo`](/docs/api/events/user-event#scrollto) over `fireEvent.scroll` for `ScrollView`, `FlatList`, and `SectionList` components. User Event simulates events more realistically based on React Native runtime behavior.
:::
```tsx
@@ -6,7 +6,7 @@ Fire Event is our original event simulation API. It can invoke **any event handl
In contrast, User Event provides realistic event simulation for user interactions like `press` or `type`. Each interaction will trigger a **sequence of events** corresponding to React Native runtime behavior. These events will be invoked **only on host elements**, and **will automatically receive event data** corresponding to each event.
If User Event supports a given interaction, you should always prefer it over the Fire Event counterpart, as it will make your tests much more realistic and, hence, reliable. In other cases, e.g., when User Event does not support the given event or when invoking event handlers on composite elements, you have to use Fire Event as the only available option.
If User Event supports a given interaction, prefer it over the Fire Event counterpart. It makes tests more realistic and reliable. When User Event doesn't support the event or you need to invoke event handlers on composite elements, use Fire Event.
## `setup()`
@@ -45,7 +45,7 @@ const user = userEvent.setup();
await user.press(element);
```
This helper simulates a press on any pressable element, e.g. `Pressable`, `TouchableOpacity`, `Text`, `TextInput`, etc. Unlike `fireEvent.press()`, a more straightforward API that will only call the `onPress` prop, this function simulates the entire press interaction in a more realistic way by reproducing the event sequence emitted by React Native runtime. This helper will trigger additional events like `pressIn` and `pressOut`.
Simulates a press on any pressable element, e.g. `Pressable`, `TouchableOpacity`, `Text`, `TextInput`, etc. Unlike `fireEvent.press()`, which only calls the `onPress` prop, this function simulates the entire press interaction by reproducing the event sequence emitted by React Native runtime. It triggers additional events like `pressIn` and `pressOut`.
This event will take a minimum of 130 ms to run due to the internal React Native logic. Consider using fake timers to speed up test execution for tests involving `press` and `longPress` interactions.
@@ -65,7 +65,7 @@ const user = userEvent.setup();
await user.longPress(element);
```
Simulates a long press user interaction. In React Native, the `longPress` event is emitted when the press duration exceeds the long press threshold (by default, 500 ms). In other aspects, this action behaves similarly to regular `press` action, e.g., by emitting `pressIn` and `pressOut` events. The press duration is customizable through the options. This should be useful if you use the `delayLongPress` prop.
Simulates a long press user interaction. In React Native, the `longPress` event is emitted when the press duration exceeds the long press threshold (by default, 500 ms). In other aspects, this action behaves similarly to regular `press` action, e.g., by emitting `pressIn` and `pressOut` events. The press duration is customizable through the options, which is useful when using the `delayLongPress` prop.
This event will, by default, take 500 ms to run. Due to internal React Native logic, it will take at least 130 ms regardless of the duration option passed. Consider using fake timers to speed up test execution for tests involving `press` and `longPress` interactions.
@@ -94,7 +94,7 @@ const user = userEvent.setup();
await user.type(textInput, 'Hello world!');
```
This helper simulates the user focusing on a `TextInput` element, typing `text` one character at a time, and leaving the element.
Simulates focusing on a `TextInput` element, typing `text` one character at a time, and leaving the element.
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
@@ -154,7 +154,7 @@ const user = userEvent.setup();
await user.clear(textInput);
```
This helper simulates the user clearing the content of a `TextInput` element.
Simulates clearing the content of a `TextInput` element.
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
@@ -198,7 +198,7 @@ const user = userEvent.setup();
await user.paste(textInput, 'Text to paste');
```
This helper simulates the user pasting given text to a `TextInput` element.
Simulates pasting text into a `TextInput` element.
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
@@ -252,7 +252,7 @@ const user = userEvent.setup();
await user.scrollTo(scrollView, { y: 100, momentumY: 200 });
```
This helper simulates the user scrolling a host `ScrollView` element.
Simulates scrolling a host `ScrollView` element.
This function supports only host `ScrollView` elements, passing other element types will result in an error. Note that `FlatList` is accepted as it renders to a host `ScrollView` element.
+15 -15
View File
@@ -14,7 +14,7 @@ There is no need to set up the built-in matchers; they are automatically availab
expect(element).toBeOnTheScreen();
```
This allows you to assert whether an element is attached to the element tree or not. If you hold a reference to an element and it gets unmounted during the test it will no longer pass this assertion.
Asserts whether an element is attached to the element tree. If you hold a reference to an element and it gets unmounted during the test, it will no longer pass this assertion.
## Element Content
@@ -30,7 +30,7 @@ expect(element).toHaveTextContent(
)
```
This allows you to assert whether the given element has the given text content or not. It accepts either `string` or `RegExp` matchers, as well as [text match options](/docs/api/queries#text-match-options) of `exact` and `normalizer`.
Asserts whether the given element has the given text content. It accepts either `string` or `RegExp` matchers, as well as [text match options](/docs/api/queries#text-match-options) of `exact` and `normalizer`.
### `toContainElement()`
@@ -40,7 +40,7 @@ expect(container).toContainElement(
)
```
This allows you to assert whether the given container element does contain another host element.
Asserts whether the given container element contains another host element.
### `toBeEmptyElement()`
@@ -48,7 +48,7 @@ This allows you to assert whether the given container element does contain anoth
expect(element).toBeEmptyElement();
```
This allows you to assert whether the given element does not have any host child elements or text content.
Asserts whether the given element has no host child elements or text content.
## Checking element state
@@ -64,7 +64,7 @@ expect(element).toHaveDisplayValue(
)
```
This allows you to assert whether the given `TextInput` element has a specified display value. It accepts either `string` or `RegExp` matchers, as well as [text match options](/docs/api/queries#text-match-options) of `exact` and `normalizer`.
Asserts whether the given `TextInput` element has a specified display value. It accepts either `string` or `RegExp` matchers, as well as [text match options](/docs/api/queries#text-match-options) of `exact` and `normalizer`.
### `toHaveAccessibilityValue()`
@@ -79,7 +79,7 @@ expect(element).toHaveAccessibilityValue(
)
```
This allows you to assert whether the given element has a specified accessible value.
Asserts whether the given element has a specified accessible value.
This matcher will assert accessibility value based on `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` and `accessibilityValue` props. Only defined value entries will be used in the assertion, the element might have additional accessibility value entries and still be matched.
@@ -92,7 +92,7 @@ expect(element).toBeEnabled();
expect(element).toBeDisabled();
```
These allow you to assert whether the given element is enabled or disabled from the user's perspective. It relies on the accessibility disabled state as set by `aria-disabled` or `accessibilityState.disabled` props. It will consider a given element disabled when it or any of its ancestors is disabled.
Asserts whether the given element is enabled or disabled from the user's perspective. It relies on the accessibility disabled state as set by `aria-disabled` or `accessibilityState.disabled` props. It considers an element disabled when it or any of its ancestors is disabled.
:::note
These matchers are the negation of each other, and both are provided to avoid double negations in your assertions.
@@ -104,7 +104,7 @@ These matchers are the negation of each other, and both are provided to avoid do
expect(element).toBeSelected();
```
This allows you to assert whether the given element is selected from the user's perspective. It relies on the accessibility selected state as set by `aria-selected` or `accessibilityState.selected` props.
Asserts whether the given element is selected from the user's perspective. It relies on the accessibility selected state as set by `aria-selected` or `accessibilityState.selected` props.
### `toBeChecked()` / `toBePartiallyChecked()` {#tobechecked}
@@ -113,7 +113,7 @@ expect(element).toBeChecked();
expect(element).toBePartiallyChecked();
```
These allow you to assert whether the given element is checked or partially checked from the user's perspective. It relies on the accessibility checked state as set by `aria-checked` or `accessibilityState.checked` props.
Asserts whether the given element is checked or partially checked from the user's perspective. It relies on the accessibility checked state as set by `aria-checked` or `accessibilityState.checked` props.
:::note
@@ -129,7 +129,7 @@ expect(element).toBeExpanded();
expect(element).toBeCollapsed();
```
These allow you to assert whether the given element is expanded or collapsed from the user's perspective. It relies on the accessibility expanded state as set by `aria-expanded` or `accessibilityState.expanded` props.
Asserts whether the given element is expanded or collapsed from the user's perspective. It relies on the accessibility expanded state as set by `aria-expanded` or `accessibilityState.expanded` props.
:::note
These matchers are the negation of each other for expandable elements (elements with explicit `aria-expanded` or `accessibilityState.expanded` props). However, both won't pass for non-expandable elements (ones without explicit `aria-expanded` or `accessibilityState.expanded` props).
@@ -141,7 +141,7 @@ These matchers are the negation of each other for expandable elements (elements
expect(element).toBeBusy();
```
This allows you to assert whether the given element is busy from the user's perspective. It relies on the accessibility busy state as set by `aria-busy` or `accessibilityState.busy` props.
Asserts whether the given element is busy from the user's perspective. It relies on the accessibility busy state as set by `aria-busy` or `accessibilityState.busy` props.
## Checking element style
@@ -151,7 +151,7 @@ This allows you to assert whether the given element is busy from the user's pers
expect(element).toBeVisible();
```
This allows you to assert whether the given element is visible from the user's perspective.
Asserts whether the given element is visible from the user's perspective.
The element is considered invisible when itself or any of its ancestors has `display: none` or `opacity: 0` styles, as well as when it's hidden from accessibility.
@@ -163,7 +163,7 @@ expect(element).toHaveStyle(
)
```
This allows you to assert whether the given element has given styles.
Asserts whether the given element has given styles.
## Other matchers
@@ -179,7 +179,7 @@ expect(element).toHaveAccessibleName(
)
```
This allows you to assert whether the given element has a specified accessible name. It accepts either `string` or `RegExp` matchers, as well as [text match options](/docs/api/queries#text-match-options) of `exact` and `normalizer`.
Asserts whether the given element has a specified accessible name. It accepts either `string` or `RegExp` matchers, as well as [text match options](/docs/api/queries#text-match-options) of `exact` and `normalizer`.
The accessible name will be computed based on `aria-labelledby`, `accessibilityLabelledBy`, `aria-label`, and `accessibilityLabel` props. For `Image` elements, the `alt` prop will also be considered. In the absence of these props, the element text content will be used.
@@ -194,7 +194,7 @@ expect(element).toHaveProp(
)
```
This allows you to assert whether the given element has a given prop. When the `value` parameter is `undefined` it will only check for existence of the prop, and when `value` is defined it will check if the actual value matches passed value.
Asserts whether the given element has a given prop. When the `value` parameter is `undefined`, it only checks for prop existence. When `value` is defined, it checks if the actual value matches the passed value.
:::note
This matcher should be treated as an escape hatch to be used when all other matchers are not suitable.
+5 -5
View File
@@ -17,13 +17,13 @@ function waitFor<T>(
): Promise<T>;
```
Waits for a period of time for the `expectation` callback to pass. `waitFor` may run the callback a number of times until timeout is reached, as specified by the `timeout` and `interval` options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of `waitFor` function.
Waits for the `expectation` callback to pass. `waitFor` runs the callback multiple times until timeout is reached, as specified by the `timeout` and `interval` options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, is treated as meeting the expectation, and the callback result is returned to the caller.
```tsx
await waitFor(() => expect(mockFunction).toHaveBeenCalledWith());
```
`waitFor` function will be executing `expectation` callback every `interval` (default: every 50 ms) until `timeout` (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to `waitFor` caller. Otherwise, when it reaches the timeout, the final error thrown by `expectation` will be re-thrown by `waitFor` to the calling code.
`waitFor` executes the `expectation` callback every `interval` (default: 50 ms) until `timeout` (default: 1000 ms) is reached. Execution stops as soon as the callback doesn't throw an error, and the callback's return value is returned to the caller. If timeout is reached, `waitFor` re-throws the final error thrown by `expectation`.
```tsx
// ❌ `waitFor` will return immediately because callback does not throw
@@ -41,7 +41,7 @@ waitFor(() => expect(1).toBe(2));
You can enforce awaiting `waitFor` by using the [await-async-utils](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md) rule from [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library).
:::
Since `waitFor` is likely to run `expectation` callback multiple times, it is highly recommended for it [not to perform any side effects](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor) in `waitFor`.
Since `waitFor` runs the `expectation` callback multiple times, [avoid performing side effects](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor) in `waitFor`.
```jsx
await waitFor(async () => {
@@ -55,7 +55,7 @@ await waitFor(async () => {
Avoiding side effects in `expectation` callback can be partially enforced with the [`no-wait-for-side-effects` rule](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md).
:::
It is also recommended to have a [single assertion per each `waitFor`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback) for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate `waitFor` calls. In many cases you won't actually need to wrap the second assertion in `waitFor` since the first one will do the waiting required for asynchronous change to happen.
Use a [single assertion per `waitFor`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback) for consistency and faster failing tests. For multiple assertions, use separate `waitFor` calls. Often you won't need to wrap the second assertion in `waitFor` since the first one waits for the asynchronous change.
`waitFor` checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:
@@ -78,7 +78,7 @@ while (fakeTimeRemaining > 0) {
throw lastError;
```
In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because `waitFor` already does this for us.
In the following example we test that a function is called after 10 seconds using fake timers. With fake timers, the test doesn't depend on real time passing, making it faster and more reliable. We don't need to advance fake timers through Jest's API because `waitFor` handles this.
```tsx
// in component
+1 -1
View File
@@ -33,7 +33,7 @@ function act<T>(callback: () => T | Promise<T>): Promise<T>;
Wraps code that causes React state updates to ensure all updates are processed before assertions. By default any `render`, `rerender`, `fireEvent`, and `waitFor` calls are wrapped by this function, so there is no need to wrap it manually.
**In v14, `act` is now async by default and always returns a Promise**, making it compatible with async React features like `Suspense` boundary or `use()` hook. This ensures all pending React updates are executed before the Promise resolves.
**In v14, `act` is now async by default and always returns a Promise**. This works with async React features like `Suspense` boundaries and the `use()` hook. All pending React updates are executed before the Promise resolves.
```ts
import { act } from '@testing-library/react-native';
+4 -4
View File
@@ -18,7 +18,7 @@ test('accessing queries using "screen" object', async () => {
})
```
The modern and recommended way of accessing queries is to use the `screen` object exported by the `@testing-library/react-native` package. This object will contain methods of all available queries bound to the most recently rendered UI.
Use the `screen` object exported by `@testing-library/react-native` to access queries. This object contains all available query methods bound to the most recently rendered UI.
### Using `render` result
@@ -31,7 +31,7 @@ test('accessing queries using "render" result', async () => {
})
```
The classic way is to capture query functions, as they are returned from the `render` function call. This provides access to the same functions as in the case of the `screen` object.
You can also capture query functions from the `render` function return value. This provides the same query functions as the `screen` object.
## Query parts
@@ -82,7 +82,7 @@ getAllByX(...): HostElement[]
queryByX(...): HostElement | null
```
`queryBy*` queries return the first matching node for a query, and return `null` if no elements match. This is useful for asserting an element that is not present. This throws if more than one match is found (use `queryAllBy` instead).
`queryBy*` queries return the first matching node for a query, or `null` if no elements match. Use these to assert that an element is not present. They throw if more than one match is found (use `queryAllBy` instead).
### `queryAllBy*` queries {#query-all-by}
@@ -375,7 +375,7 @@ const element = screen.getByTestId('unique-id');
```
:::info
In the spirit of [the guiding principles](https://testing-library.com/docs/guiding-principles), it is recommended to use this only after the other queries don't work for your use case. Using `testID` attributes do not resemble how your software is used and should be avoided if possible. However, they are particularly useful for end-to-end testing on real devices, e.g. using Detox and it's an encouraged technique to use there. Learn more from the blog post ["Making your UI tests resilient to change"](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change).
Following [the guiding principles](https://testing-library.com/docs/guiding-principles), use this only when other queries don't work for your use case. `testID` attributes don't resemble how your software is used and should be avoided when possible. They're useful for end-to-end testing on real devices, e.g. with Detox. Learn more from the blog post ["Making your UI tests resilient to change"](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change).
:::
### Common options
+6 -6
View File
@@ -12,13 +12,13 @@ let screen: {
};
```
The `screen` object offers a recommended way to access queries and utilities for the currently rendered UI.
The `screen` object provides access to queries and utilities for the currently rendered UI.
This object is assigned after the `render` call and cleared after each test by calling [`cleanup`](/docs/api/misc/other#cleanup). If no `render` call has been made in a given test, then it holds a special object and throws a helpful error on each property and method access.
### `...queries`
The most important feature of `screen` is providing a set of helpful queries that allow you to find certain elements in the view hierarchy.
The main feature of `screen` is its queries for finding elements in the view hierarchy.
See [Queries](/docs/api/queries) for a complete list.
@@ -41,7 +41,7 @@ function rerender(element: React.Element<unknown>): Promise<void>;
Re-render the in-memory tree with a new root element. This simulates a React update render at the root. If the new element has the same type (and `key`) as the previous element, the tree will be updated; otherwise, it will re-mount a new tree, in both cases triggering the appropriate lifecycle events.
This method is async and uses async `act` function internally to ensure all pending React updates are executed during updating, making it compatible with async React features like `Suspense` boundary or `use()` hook.
This method is async and uses async `act` internally to execute all pending React updates during updating. This works with async React features like `Suspense` boundaries and the `use()` hook.
```jsx
import { render, screen } from '@testing-library/react-native';
@@ -62,7 +62,7 @@ function unmount(): Promise<void>;
Unmount the in-memory tree, triggering the appropriate lifecycle events.
This method is async and uses async `act` function internally to ensure all pending React updates are executed during unmounting, making it compatible with async React features like `Suspense` boundary or `use()` hook.
This method is async and uses async `act` internally to execute all pending React updates during unmounting. This works with async React features like `Suspense` boundaries and the `use()` hook.
:::note
@@ -150,7 +150,7 @@ const container: HostElement;
Returns a pseudo-element container whose children are the elements you asked to render. This is the root container element from [Test Renderer](https://github.com/mdjastrzebski/test-renderer).
The `container` is safe to use and provides access to the entire rendered tree. It's useful when you need to query or manipulate the entire rendered output, similar to how `container` works in [React Testing Library](https://testing-library.com/docs/react-testing-library/other#container-1).
The `container` provides access to the entire rendered tree. Use it to query or manipulate the rendered output, similar to how `container` works in [React Testing Library](https://testing-library.com/docs/react-testing-library/other#container-1).
```jsx
import { render, screen } from '@testing-library/react-native';
@@ -171,7 +171,7 @@ const root: HostElement | null;
Returns the rendered root [host element](/docs/advanced/testing-env#host-and-composite-components), or `null` if nothing was rendered. This is the first child of the `container`, which represents the actual root element you rendered.
This API is primarily useful for component tests, as it allows you to access root host view without using `*ByTestId` queries or similar methods.
This API is useful for component tests where you need to access the root host view without using `*ByTestId` queries or similar methods.
:::note
+12 -12
View File
@@ -13,27 +13,27 @@ and event APIs ([User Event](/docs/api/events/user-event), [Fire Event](/docs/ap
You can learn more about our testing environment [here](/docs/advanced/testing-env).
This approach has specific benefits and shortfalls. On the positive side:
This approach has benefits and limitations:
- it allows testing most of the logic of regular React Native apps
- it allows running tests on any OS supported by Jest or other test runners, e.g., on CI
- it uses much less resources than full runtime simulation
- you can use Jest fake timers
Benefits:
- Tests most of the logic of regular React Native apps
- Runs tests on any OS supported by Jest or other test runners, e.g., on CI
- Uses fewer resources than full runtime simulation
- Works with Jest fake timers
On the negative side:
Limitations:
- Cannot test native features
- May not perfectly simulate certain JavaScript features, but we're working on it
- you cannot test native features
- it might not perfectly simulate certain JavaScript features, but we are working on it
The [User Event interactions](/docs/api/events/user-event) solve some of the simulation issues, as they offer more realistic event handling than the basic [Fire Event API](/docs/api/events/fire-event).
The [User Event interactions](/docs/api/events/user-event) solve some simulation issues by handling events more realistically than the basic [Fire Event API](/docs/api/events/fire-event).
## Should I use/migrate to `screen` queries?
There is no need to migrate existing test code to use `screen`-bases queries. You can still use
queries and other functions returned by `render`. The `screen` object captures the latest `render` result.
For new code, you are encouraged to use `screen` as there are some good reasons for that, which are described in [this article](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen) by Kent C. Dodds.
For new code, use `screen`. [This article](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen) by Kent C. Dodds explains why.
## Should I use/migrate to User Event interactions?
We encourage you to migrate existing tests to use the [User Event interactions](/docs/api/events/user-event), which offer more realistic event handling than the basic [Fire Event API](/docs/api/events/fire-event). Hence, it will provide more confidence in the quality of your code.
Migrate existing tests to use the [User Event interactions](/docs/api/events/user-event), which handle events more realistically than the basic [Fire Event API](/docs/api/events/fire-event). This provides more confidence in your code quality.
@@ -1,6 +1,6 @@
# How should I query?
React Native Testing Library provides various query types, allowing great flexibility in finding views appropriate for your tests. At the same time, the number of queries might be confusing. This guide aims to help you pick the correct queries for your test scenarios.
React Native Testing Library provides various query types for finding views in tests. The number of queries can be confusing. This guide helps you pick the right queries for your test scenarios.
## Query parts
@@ -58,13 +58,13 @@ The query predicate describes how you decide whether to match the given element.
### Idiomatic query predicates
Choosing the proper query predicate helps better express the test's intent and make the tests resemble how users interact with your code (components, screens, etc.) as much as possible following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). Additionally, most predicates promote the usage of proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements.
Choosing the right query predicate helps express test intent and makes tests resemble how users interact with your code (components, screens, etc.), following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). Most predicates also promote proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements.
It is recommended to use query predicates in the following order of priority:
Use query predicates in the following order of priority:
### 1. By Role query {#by-role-query}
The first and most versatile predicate is [`*ByRole`](/docs/api/queries#by-role), which starts with the semantic role of the element and can be further narrowed down with additional options. React Native has two role systems, the web/ARIA-compatible one based on [`role`](https://reactnative.dev/docs/accessibility#role) prop and the traditional one based on [`accessibilityRole`](https://reactnative.dev/docs/accessibility#accessibilityrole) prop, you can use either of these.
The [`*ByRole`](/docs/api/queries#by-role) predicate starts with the semantic role of the element and can be narrowed down with additional options. React Native has two role systems: the web/ARIA-compatible one based on [`role`](https://reactnative.dev/docs/accessibility#role) prop and the traditional one based on [`accessibilityRole`](https://reactnative.dev/docs/accessibility#accessibilityrole) prop. You can use either.
In most cases, you need to set accessibility roles explicitly (or your component library can set some of them for you). These roles allow assistive technologies (like screen readers) and testing code to understand your view hierarchy better.
@@ -122,4 +122,4 @@ These queries include:
As a final predicate, you can use the `testID` prop to find relevant views. Using the [`*ByTestId`](/docs/api/queries#by-test-id) predicate offers the most flexibility, but at the same time, it does not represent the user experience, as users are not aware of test IDs.
Note that using test IDs is a widespread technique in end-to-end testing due to various issues with querying views through other means **in its specific context**. Nevertheless, we still encourage you to use recommended RNTL queries as it will make your integration and component test more reliable and resilient.
Note that using test IDs is common in end-to-end testing due to various issues with querying views through other means **in that specific context**. For integration and component tests, use the recommended RNTL queries to make tests more reliable and resilient.
@@ -4,9 +4,9 @@ This guide describes common issues found by users when integrating React Native
## Example repository
We maintain an [example repository](https://github.com/callstack/react-native-testing-library/tree/main/examples/basic) that showcases a modern React Native Testing Library setup with TypeScript, etc.
We maintain an [example repository](https://github.com/callstack/react-native-testing-library/tree/main/examples/basic) with a React Native Testing Library setup using TypeScript.
In case something does not work in your setup you can refer to this repository for recommended configuration.
If something doesn't work in your setup, check this repository for configuration examples.
## Undefined component error
@@ -24,7 +24,7 @@ jest.mock('@react-navigation/native', () => {
The above mock will mock `useNavigation` hook as intended, but at the same time all other exports from `@react-navigation/native` package are now `undefined`. If you want to use `NavigationContainer` component from the same package it will be `undefined` and result in the error above.
In order to mock only a part of given package you should re-export all other exports using `jest.requireActual` helper:
To mock only part of a package, re-export all other exports using `jest.requireActual`:
```ts
jest.mock('@react-navigation/native', () => {
@@ -41,7 +41,7 @@ Alternatively, you can use `jest.spyOn` to mock package exports selectively.
### Mocking React Native
In case of mocking `react-native` package you should not mock the whole package at once, as this approach has issues with `jest.requireActual` call. In this case it is recommended to mock particular library paths inside the package, e.g.:
When mocking the `react-native` package, don't mock the whole package at once, as this has issues with `jest.requireActual`. Mock specific library paths inside the package instead, e.g.:
```ts title=jest-setup.ts
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');
+3 -3
View File
@@ -32,7 +32,7 @@ This library has a peer dependency on [Test Renderer](https://github.com/mdjastr
}}
/>
Test Renderer provides better compatibility with React 19 and improved type safety compared to the deprecated [React Test Renderer](https://reactjs.org/docs/test-renderer.html).
Test Renderer has better compatibility with React 19 and improved type safety compared to the deprecated [React Test Renderer](https://reactjs.org/docs/test-renderer.html).
### Jest matchers
@@ -40,7 +40,7 @@ RNTL automatically extends Jest with React Native-specific matchers. The only th
### ESLint plugin
We recommend setting up [`eslint-plugin-testing-library`](https://github.com/testing-library/eslint-plugin-testing-library) package to help you avoid common Testing Library mistakes and bad practices.
Set up [`eslint-plugin-testing-library`](https://github.com/testing-library/eslint-plugin-testing-library) to avoid common Testing Library mistakes and bad practices.
Install the plugin (assuming you already have `eslint` installed & configured):
@@ -53,7 +53,7 @@ Install the plugin (assuming you already have `eslint` installed & configured):
}}
/>
Then, add relevant entry to your ESLint config (e.g., `.eslintrc.js`). We recommend extending the `react` plugin:
Then, add this to your ESLint config (e.g., `.eslintrc.js`). Extend the `react` plugin:
```js title=.eslintrc.js
module.exports = {