Skip to content

docs: Jest matchers docs #1506

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

## 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 testbase 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. 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.

## This solution

Expand Down Expand Up @@ -50,7 +50,7 @@ This library has a `peerDependencies` listing for `react-test-renderer`. Make su

### Additional Jest matchers

In order to use additional React Native-specific jest matchers from [@testing-library/jest-native](https://github.com/testing-library/jest-native) package add it to your project:
To use additional React Native-specific jest matchers from [@testing-library/jest-native](https://github.com/testing-library/jest-native) package add it to your project:

#### Using `yarn`

Expand All @@ -64,7 +64,7 @@ yarn add --dev @testing-library/jest-native
npm install --save-dev @testing-library/jest-native
```

Then automatically add it to your jest tests by using `setupFilesAfterEnv` option in your Jest configuration (it's usually located either in `package.json` under `"jest"` key or in a `jest.config.json` file):
Then automatically add it to your jest tests by using the `setupFilesAfterEnv` option in your Jest configuration (it's usually located either in `package.json` under the `"jest"` key or in a `jest.config.json` file):

```json
{
Expand All @@ -75,9 +75,9 @@ Then automatically add it to your jest tests by using `setupFilesAfterEnv` optio

### Custom Jest Preset (React Native before 0.71)

We generally advise to use the "react-native" preset when testing with this library.
We generally advise using the "react-native" preset when testing with this library.

However, if you use React Native version earlier than 0.71 with [modern Jest fake timers](https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers) (default since Jest 27), you'll need to apply this custom Jest preset or otherwise awaiting promises, like using `waitFor` or `findBy*`, queries will fail with timeout.
However, if you use React Native version earlier than 0.71 with [modern Jest fake timers](https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers) (default since Jest 27), you'll need to apply this custom Jest preset or otherwise awaiting promises, like using `waitFor` or `findBy*`, queries will fail with a timeout.

This is a [known issue](https://github.com/facebook/react-native/issues/29303). It happens because React Native's Jest preset overrides native Promise. Our preset restores it to defaults, which is not a problem in most apps out there.

Expand Down Expand Up @@ -130,11 +130,11 @@ You can find the source of `QuestionsBoard` component and this example [here](ht

The [public API](https://callstack.github.io/react-native-testing-library/docs/api) of `@testing-library/react-native` is focused around these essential methods:

- [`render`](https://callstack.github.io/react-native-testing-library/docs/api#render) – deeply renders given React element and returns helpers to query the output components.
- [`render`](https://callstack.github.io/react-native-testing-library/docs/api#render) – deeply renders the given React element and returns helpers to query the output components.
- [`fireEvent`](https://callstack.github.io/react-native-testing-library/docs/api#fireevent) - invokes named event handler on the element.
- [`waitFor`](https://callstack.github.io/react-native-testing-library/docs/api#waitfor) - waits for non-deterministic periods of time until queried element is added or times out.
- [`waitForElementToBeRemoved`](https://callstack.github.io/react-native-testing-library/docs/api#waitforelementtoberemoved) - waits for non-deterministic periods of time until queried element is removed or times out.
- [`within`](https://callstack.github.io/react-native-testing-library/docs/api#within) - creates a queries object scoped for given element.
- [`waitFor`](https://callstack.github.io/react-native-testing-library/docs/api#waitfor) - waits for non-deterministic periods of time until the queried element is added or times out.
- [`waitForElementToBeRemoved`](https://callstack.github.io/react-native-testing-library/docs/api#waitforelementtoberemoved) - waits for non-deterministic periods of time until the queried element is removed or times out.
- [`within`](https://callstack.github.io/react-native-testing-library/docs/api#within) - creates a queries object scoped for a given element.

## Migration Guides

Expand All @@ -150,7 +150,7 @@ The [public API](https://callstack.github.io/react-native-testing-library/docs/a

## Related External Resources

- [Real world extensive examples repo](https://github.com/vanGalilea/react-native-testing)
- [Real-world extensive examples repo](https://github.com/vanGalilea/react-native-testing)
- [Where and how to start testing 🧪 your react-native app ⚛️ and how to keep on testin’](https://blog.usejournal.com/where-and-how-to-start-testing-your-react-native-app-%EF%B8%8F-and-how-to-keep-on-testin-ec3464fb9b41)
- [Intro to React Native Testing Library & Jest Native](https://youtu.be/CpTQb0XWlRc)

Expand Down
216 changes: 216 additions & 0 deletions website/docs/JestMatchers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
id: jest-matchers
title: Jest Matchers
---

:::note
Built-in Jest matchers require RNTL v12.4.0 or later.
:::

This guide describes built-in Jest matchers, we recommend using these matchers as they provide more readable tests, better accessibility support, and a better developer experience.

If you are already using legacy Jest Native matchers we have a [migration guide](migration-jest-native) for moving to the built-in matchers.

import TOCInline from '@theme/TOCInline';

<TOCInline toc={toc} />

## Element Existence

### `toBeOnTheScreen()`

```ts
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.

## Element Content

### `toHaveTextContent()`

```ts
expect(element).toHaveTextContent(
text: string | RegExp,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
},
)
```

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](Queries.md#text-match-options) of `exact` and `normalizer`.

### `toContainElement()`

```ts
expect(container).toContainElement(
element: ReactTestInstance | null,
)
```

This allows you to assert whether the given container element does contain another host element.

### `toBeEmptyElement()`

```ts
expect(element).toBeEmptyElement()
```

This allows you to assert whether the given element does not have any host child elements or text content.





## Element State

### `toHaveDisplayValue()`

```ts
expect(element).toHaveDisplayValue(
value: string | RegExp,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
},
)
```

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](Queries.md#text-match-options) of `exact` and `normalizer`.

### `toHaveAccessibilityValue()`

```ts
expect(element).toHaveAccessibilityValue(
value: {
min?: number;
max?: number;
now?: number;
text?: string | RegExp;
},
)
```

This allows you to assert 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.

When querying by `text` entry a string or `RegExp` might be used.


### `toBeEnabled()` / `toBeDisabled` {#tobeenabled}

```ts
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.

:::note
These matchers are the negation of each other, and both are provided to avoid double negations in your assertions.
:::


### `toBeSelected()`

```ts
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.


### `toBeChecked()` / `toBePartiallyChecked()` {#tobechecked}

```ts
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.

:::note
* `toBeChecked()` matcher works only on elements with the `checkbox` or `radio` role.
* `toBePartiallyChecked()` matcher works only on elements with the `checkbox` role.
:::

### `toBeExpanded()` / `toBeCollapsed()` {#tobeexpanded}

```ts
expect(element).toBeExpanded()
expect(element).toBeCollapsed()
```

These allows you to assert whether the given element is expanded or collapsed from the user's perspective. It relies on the accessibility disabled 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).
:::


### `toBeBusy()`

```ts
expect(element).toBeBusy()
```

This allows you to assert whether the given element is busy from the user's perspective. It relies on the accessibility selected state as set by `aria-busy` or `accessibilityState.busy` props.

## Element Styles

### `toBeVisible()`

```ts
expect(element).toBeVisible()
```

This allows you to assert 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.

### `toHaveStyle()`

```ts
expect(element).toHaveStyle(
style: StyleProp<Style>,
)
```

This allows you to assert whether the given element has given styles.

## Other

### `toHaveAccessibleName()`

```ts
expect(element).toHaveAccessibleName(
name?: string | RegExp,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
},
)
```

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](Queries.md#text-match-options) of `exact` and `normalizer`.

The accessible name will be computed based on `aria-labelledby`, `accessibilityLabelledBy`, `aria-label`, and `accessibilityLabel` props, in the absence of these props, the element text content will be used.

When the `name` parameter is `undefined` it will only check if the element has any accessible name.

### `toHaveProp()`

```ts
expect(element).toHaveProp(
name: string,
value?: unknown,
)
```

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.

:::note
This matcher should be treated as an escape hatch to be used when all other matchers are not suitable.
:::
79 changes: 79 additions & 0 deletions website/docs/MigrationJestMatchers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
id: migration-jest-native
title: Migration from Jest Native matchers
---

This guide describes the steps necessary to migrate from [legacy Jest Native matchers v5](https://github.com/testing-library/jest-native) to [built-in Jest matchers](jest-matchers).

import TOCInline from '@theme/TOCInline';

<TOCInline toc={toc} />

## General notes

All of the built-in Jest matchers provided by the React Native Testing Library support only host elements. This should not be an issue, as all RNTL v12 queries already return only host elements. When this guide states that a given matcher should work the same it assumes behavior only host elements. If you need to assert the status of composite elements use Jest Native matchers in [legacy mode](#gradual-migration).

## Usage

You can use the built-in matchers by adding the following line to your `jest-setup.ts` file:

```ts
import '@testing-library/react-native/extend-expect';
```

### Gradual migration

You can use the built-in matchers alongside legacy Jest Native matchers by changing their import in your `jest-setup.ts` file:

```ts
// Replace this:
// import '@testing-library/jest-native/extend-expect';

// With this:
import '@testing-library/react-native/extend-expect';
import '@testing-library/jest-native/legacy-extend-expect';
```

In this case legacy matchers will be available using the `legacy_` prefix, e.g.:

```ts
expect(element).legacy_toHaveAccessibilityState({ busy: true });
```

## Migration details

### Matchers not requiring changes

The following matchers should work the same:
* [`toBeEmptyElement()`](jest-matchers#tobeemptyelement)
* [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled)
* [`toBeOnTheScreen()`](jest-matchers#tobeonthescreen)
* [`toBeVisible()`](jest-matchers#tobevisible)
* [`toContainElement()`](jest-matchers#tocontainelement)
* [`toHaveAccessibilityValue()`](jest-matchers#tohaveaccessibilityvalue)
* [`toHaveDisplayValue()`](jest-matchers#tohavedisplayvalue)
* [`toHaveProp()`](jest-matchers#tohaveprop)
* [`toHaveStyle()`](jest-matchers#tohavestyle)
* [`toHaveTextContent()`](jest-matchers#tohavetextcontent)

### Replaced matchers

The `toHaveAccessibilityState()` matcher has been replaced by the following matchers:
* enabled state: [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled)
* checked state: [`toBeChecked()` / `toBePartiallyChecked()`](jest-matchers#tobechecked)
* selected state: [`toBeSelected()`](jest-matchers#tobeselected)
* expanded state: [`toBeExpanded()` / `toBeCollapsed()`](jest-matchers#tobeexpanded)
* busy state: [`toBeBusy()`](jest-matchers#tobebusy)

The new matchers support both `accessibilityState` and `aria-*` props.

### Added matchers

New [`toHaveAccessibleName()`](jest-matchers#tohaveaccessiblename) has been added.

### Noteworthy details

You should be aware of the following details:
* [`toBeEnabled()` / `toBeDisabled()`](jest-matchers#tobeenabled) matchers also check the disabled state for the element's ancestors and not only the element itself. This is the same as in legacy Jest Native matchers of the same name but differs from the removed `toHaveAccessibilityState()` matcher.
* [`toBeChecked()`](jest-matchers#tobechecked) matcher supports only elements with a `checkbox` or `radio` role
* [`toBePartiallyChecked()`](jest-matchers#tobechecked) matcher supports only elements with `checkbox` role
Loading