From 6f950efd75bf2751be9e4fbb5d57746b1261eacd Mon Sep 17 00:00:00 2001 From: Alex Krolick Date: Mon, 31 Dec 2018 11:17:13 -0800 Subject: [PATCH] chore: move README docs to new site --- README.md | 1198 ++--------------------------------------------------- 1 file changed, 27 insertions(+), 1171 deletions(-) diff --git a/README.md b/README.md index 2efb13fb..194487bc 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@

Simple and complete React DOM testing utilities that encourage good testing practices.

+[**Read The Docs**](https://react-testing-library-docs.netlify.com/react) | [Edit the docs](https://github.com/alexkrolick/react-testing-library-docs) +
@@ -31,6 +33,27 @@ +## Table of Contents + + + + +- [The Problem](#the-problem) +- [This Solution](#this-solution) +- [Example](#example) +- [Installation](#installation) +- [Examples](#examples) +- [Other Solutions](#other-solutions) +- [Guiding Principles](#guiding-principles) +- [Contributors](#contributors) +- [Issues](#issues) + - [šŸ› Bugs](#-bugs) + - [šŸ’” Feature Requests](#-feature-requests) + - [ā“ Questions](#-questions) +- [LICENSE](#license) + + + ## The problem You want to write maintainable tests for your React components. As a part of @@ -51,40 +74,6 @@ primary guiding principle is: > [The more your tests resemble the way your software is used, the more > confidence they can give you.][guiding-principle] -So rather than dealing with instances of rendered react components, your tests -will work with actual DOM nodes. The utilities this library provides facilitate -querying the DOM in the same way the user would. Finding for elements by their -label text (just like a user would), finding links and buttons from their text -(like a user would). It also exposes a recommended way to find elements by a -`data-testid` as an "escape hatch" for elements where the text content and label -do not make sense or is not practical. - -This library encourages your applications to be more accessible and allows you -to get your tests closer to using your components the way a user will, which -allows your tests to give you more confidence that your application will work -when a real user uses it. - -This library is a replacement for [enzyme](http://airbnb.io/enzyme/). While you -_can_ follow these guidelines using enzyme itself, enforcing this is harder -because of all the extra utilities that enzyme provides (utilities which -facilitate testing implementation details). Read more about this in -[the FAQ](#faq) below. - -**What this library is not**: - -1. A test runner or framework -2. Specific to a testing framework (though we recommend Jest as our preference, - the library works with any framework. See - [Using Without Jest](https://github.com/kentcdodds/dom-testing-library#using-without-jest)) - -> NOTE: This library is built on top of -> [`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library) -> which is where most of the logic behind the queries is. - -## What is react-testing-library? - -Have a look at the video below for an explanation.

-[![what is react testing library](https://img.youtube.com/vi/JKOwJUM4_RM/0.jpg)](https://youtu.be/JKOwJUM4_RM 'what is react testing library') ## Example @@ -134,41 +123,6 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl }) ``` -## Table of Contents - - - - -- [Installation](#installation) -- [Setup](#setup) - - [Global Config](#global-config) - - [Custom Render](#custom-render) -- [Usage](#usage) - - [`render`](#render) - - [`cleanup`](#cleanup) - - [`flushEffects` (experimental)](#flusheffects-experimental) -- [`dom-testing-library` APIs](#dom-testing-library-apis) - - [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event) - - [`waitForElement`](#waitforelement) - - [`wait`](#wait) - - [`within`](#within) -- [`TextMatch`](#textmatch) -- [`query` APIs](#query-apis) -- [`queryAll` and `getAll` APIs](#queryall-and-getall-apis) -- [Examples](#examples) -- [Learning Material](#learning-material) -- [FAQ](#faq) -- [Other Solutions](#other-solutions) -- [Guiding Principles](#guiding-principles) -- [Contributors](#contributors) -- [Issues](#issues) - - [šŸ› Bugs](#-bugs) - - [šŸ’” Feature Requests](#-feature-requests) - - [ā“ Questions](#-questions) -- [LICENSE](#license) - - - ## Installation This module is distributed via [npm][npm] which is bundled with [node][node] and @@ -183,761 +137,15 @@ This library has a `peerDependencies` listing for `react-dom`. You may also be interested in installing `jest-dom` so you can use [the custom jest matchers](https://github.com/gnapse/jest-dom#readme). -## Setup - -`react-testing-library` does not require any configuration to be used (as -demonstrated in the example above). However, there are some things you can do -when configuring your testing framework to reduce some boilerplate. In these -docs we'll demonstrate configuring Jest, but you should be able to do similar -things with any testing framework (react-testing-library does not require that -you use Jest). - -### Global Config - -There are several options you can add to your global test config that simplify -the setup and teardown of tests in individual files. For example, you can ensure -[`cleanup`](#cleanup) is called after each test and import additional -assertions. - -To do this with Jest, you can add the -[`setupTestFrameworkScriptFile`](https://facebook.github.io/jest/docs/en/configuration.html#setuptestframeworkscriptfile-string) -option to your Jest config. The setup file can be anywhere, for example -`jest.setup.js` or `./utils/setupTests.js`. - -If you are using the default setup from create-react-app, this option is set to -`src/setupTests.js`. You should create this file if it doesn't exist and put the -setup code there. - -```javascript -// jest.config.js -module.exports = { - setupTestFrameworkScriptFile: require.resolve('./jest.setup.js'), - // ... other options ... -} -``` - -```javascript -// jest.setup.js - -// add some helpful assertions -import 'jest-dom/extend-expect' - -// this is basically: afterEach(cleanup) -import 'react-testing-library/cleanup-after-each' -``` - -### Custom Render - -It's often useful to define a custom render method that includes things like -global context providers, data stores, etc. To make this available globally, one -approach is to define a utility file that re-exports everything from -`react-testing-library`. You can replace react-testing-library with this file in -all your imports. - -```diff -// my-component.test.js -- import { render, fireEvent } from 'react-testing-library'; -+ import { render, fireEvent } from '../test-utils'; -``` - -```js -// test-utils.js -import {render} from 'react-testing-library' -import {ThemeProvider} from 'my-ui-lib' -import {TranslationProvider} from 'my-i18n-lib' -import defaultStrings from 'i18n/en-x-default' - -const customRender = (node, options) => { - return render( - - - {node} - - , - options, - ) -} - -// re-export everything -export * from 'react-testing-library' - -// override render method -export {customRender as render} -``` - -To make this file accessible without using relative imports, add the folder -containing the file to the Jest `moduleDirectories` option. Note: this will make -_all_ the .js files in that directory importable without `../`. - -```diff -// my-component.test.js -- import { render, fireEvent } from '../test-utils'; -+ import { render, fireEvent } from 'test-utils'; -``` - -```diff -// jest.config.js -module.exports = { - moduleDirectories: [ - 'node_modules', -+ // add the directory with the test-utils.js file, for example: -+ 'utils', // a utility folder -+ __dirname, // the root directory - ], - // ... other options ... -} -``` - -If your project is based on top of Create React App, to make the file accessible -without using relative imports, you just need to create a `.env` file in the -root of your project with the following configuration: - -``` -// Create React App project structure - -$ app -. -ā”œā”€ā”€ .env -ā”œā”€ā”€ src -│ ā”œā”€ā”€ utils -│ │ └── test-utils.js -│ -``` - -``` -// .env - -// example if your utils folder is inside the /src directory. -NODE_PATH=src/utils -``` - -There is the case when you want to wrap your components in a `Provider`, this -might cause conflicts when `rerender`ed. To achieve this, we suggest the -`rerender` should be implemented the same way custom queries, by changing the -return value of the customRender. - -```js -// test-utils.js - -const customRender = (ui, options) => { - const rendered = render(
{ui}
, options) - return { - ...rendered, - rerender: newUi => - customRender(newUi, { - container: rendered.container, - baseElement: rendered.baseElement, - }), - } -} -``` - -#### Export Issue with Babel Versions Lower Than 7 - -Babel versions lower than 7 throw an error when trying to override the named -export in the example above. (See -[#169](https://github.com/kentcdodds/react-testing-library/issues/169).) - -
-Workaround - -You can use CommonJS modules instead of ES modules, which should work in Node: - -```js -// test-utils.js -const rtl = require('react-testing-library') - -const customRender = (node, options) => { - return rtl.render({node}) -} - -module.exports = { - ...rtl, - render: customRender, -} -``` - -
- -## Usage - -### `render` - -Defined as: - -```typescript -function render( - ui: React.ReactElement, - options?: { - /* You won't often use this, expand below for docs on options */ - }, -): RenderResult -``` - -Render into a container which is appended to `document.body`. It should be used -with [cleanup](#cleanup): - -```javascript -import {render} from 'react-testing-library' - -render(
) -``` - -
- -Expand to see documentation on the options - -You wont often need to specify options, but if you ever do, here are the -available options which you could provide as a second argument to `render`. - -**container**: By default, `react-testing-library` will create a `div` and -append that div to the `document.body` and this is where your react component -will be rendered. If you provide your own HTMLElement `container` via this -option, it will not be appended to the `document.body` automatically. - -For Example: If you are unit testing a `tablebody` element, it cannot be a child -of a `div`. In this case, you can specify a `table` as the render `container`. - -```javascript -const table = document.createElement('table') - -const {container} = render(, { - container: document.body.appendChild(table), -}) -``` - -**baseElement**: If the `container` is specified, then this defaults to that, -otherwise this defaults to `document.documentElement`. This is used as the base -element for the queries as well as what is printed when you use `debug()`. - -**hydrate**: If hydrate is set to true, then it will render with -[ReactDOM.hydrate](https://reactjs.org/docs/react-dom.html#hydrate). This may be -useful if you are using server-side rendering and use ReactDOM.hydrate to mount -your components. - -
- -In the example above, the `render` method returns an object that has a few -properties: - -#### `container` - -The containing DOM node of your rendered React Element (rendered using -`ReactDOM.render`). It's a `div`. This is a regular DOM node, so you can call -`container.querySelector` etc. to inspect the children. - -> Tip: To get the root element of your rendered element, use -> `container.firstChild`. -> -> NOTE: When that root element is a -> [React Fragment](https://reactjs.org/docs/fragments.html), -> `container.firstChild` will only get the first child of that Fragment, not the -> Fragment itself. - -> 🚨 If you find yourself using `container` to query for rendered elements then -> you should reconsider! The other queries are designed to be more resiliant to -> changes that will be made to the component you're testing. Avoid using -> `container` to query for elements! - -#### `baseElement` - -The containing DOM node where your React Element is rendered in the container. -If you don't specify the `baseElement` in the options of `render`, it will -default to `document.body`. - -This is useful when the component you want to test renders something outside the -container div, e.g. when you want to snapshot test your portal component which -renders it's HTML directly in the body. - -> Note: the queries returned by the `render` looks into baseElement, so you can -> use queries to test your portal component without the baseElement. - -#### `debug` - -This method is a shortcut for `console.log(prettyDOM(baseElement))`. - -```javascript -import React from 'react' -import {render} from 'react-testing-library' - -const HelloWorld = () =>

Hello World

-const {debug} = render() -debug() -//
-//

Hello World

-//
-// you can also pass an element: debug(getByTestId('messages')) -``` - -This is a simple wrapper around `prettyDOM` which is also exposed and comes from -[`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#prettydom). - -#### `rerender` - -It'd probably be better if you test the component that's doing the prop updating -to ensure that the props are being updated correctly (see -[the Guiding Principles section](#guiding-principles)). That said, if you'd -prefer to update the props of a rendered component in your test, this function -can be used to update props of the rendered component. - -```javascript -import {render} from 'react-testing-library' - -const {rerender} = render() - -// re-render the same component with different props -rerender() -``` - -[Open the tests](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/update-props.js) -for a full example of this. - -#### `unmount` - -This will cause the rendered component to be unmounted. This is useful for -testing what happens when your component is removed from the page (like testing -that you don't leave event handlers hanging around causing memory leaks). - -> This method is a pretty small abstraction over -> `ReactDOM.unmountComponentAtNode` - -```javascript -import {render} from 'react-testing-library' - -const {container, unmount} = render() -unmount() -// your component has been unmounted and now: container.innerHTML === '' -``` - -#### `getByLabelText(text: TextMatch, options): HTMLElement` - -> Options: -> `{selector = '*', exact = true, collapseWhitespace = true, trim = true}` - -This will search for the label that matches the given [`TextMatch`](#textmatch), -then find the element associated with that label. - -```javascript -import {render} from 'react-testing-library' - -const {getByLabelText} = render() -const inputNode = getByLabelText('Username') - -// this would find the input node for the following DOM structures: -// The "for" attribute (NOTE: in JSX with React you'll write "htmlFor" rather than "for") -// -// -// -// The aria-labelledby attribute -// -// -// -// Wrapper labels -// -// -// It will NOT find the input node for this: -// -// -// For this case, you can provide a `selector` in the options: -const inputNode = getByLabelText('username', {selector: 'input'}) -// and that would work -// Note that will also work, but take -// care because this is not a label that users can see on the page. So -// the purpose of your input should be obvious for those users. -``` - -> Note: This method will throw an error if it cannot find the node. If you don't -> want this behavior (for example you wish to assert that it doesn't exist), -> then use `queryByLabelText` instead. - -#### `getByPlaceholderText(text: TextMatch, options): HTMLElement` - -> Options: `{exact = true, collapseWhitespace = true, trim = true}` - -This will search for all elements with a placeholder attribute and find one that -matches the given [`TextMatch`](#textmatch). - -```javascript -import {render} from 'react-testing-library' - -const {getByPlaceholderText} = render() -const inputNode = getByPlaceholderText('Username') -``` - -> NOTE: a placeholder is not a good substitute for a label so you should -> generally use `getByLabelText` instead. - -#### `getByText(text: TextMatch, options): HTMLElement` - -> Options: -> `{selector = '*', exact = true, collapseWhitespace = true, trim = true, ignore = 'script, style'}` - -This will search for all elements that have a text node with `textContent` -matching the given [`TextMatch`](#textmatch). - -```javascript -import {render} from 'react-testing-library' - -const {getByText} = render(About ā„¹ļø) -const aboutAnchorNode = getByText('about') -``` - -#### `getByAltText(text: TextMatch, options): HTMLElement` - -> Options: `{exact = true, collapseWhitespace = true, trim = true}` - -This will return the element (normally an ``) that has the given `alt` -text. Note that it only supports elements which accept an `alt` attribute: -[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img), -[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input), -and [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area) -(intentionally excluding -[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/applet) -as it's deprecated). - -```javascript -import {render} from 'react-testing-library' - -const {getByAltText} = render( - Incredibles 2 Poster, -) -const incrediblesPosterImg = getByAltText(/incredibles.*poster$/i) -``` - -#### `getByTestId(text: TextMatch, options): HTMLElement` - -> Options: `{exact = true, collapseWhitespace = true, trim = true}` - -A shortcut to `` container.querySelector(`[data-testid="${yourId}"]`) `` (and it -also accepts a [`TextMatch`](#textmatch)). - -```javascript -import {render} from 'react-testing-library' - -const {getByTestId} = render() -const usernameInputElement = getByTestId('username-input') -``` - -> In the spirit of [the guiding principles](#guiding-principles), it is -> recommended to use this only after `getByLabel`, `getByPlaceholderText` or -> `getByText` don't work for your use case. Using `data-testid` attributes do -> not resemble how your software is used and should be avoided if possible. That -> said, they are _way_ better than querying based on DOM structure. Learn more -> about `data-testid`s from the blog post ["Making your UI tests resilient to -> change"][data-testid-blog-post] - -
- What if my project already uses data-test-id or another attribute? - Do I have to migrate to data-testid? - - -If you're starting out with a new codebase, it's recommended that you stick with -data-testid, following the precedent set by -[React Native Web](https://github.com/kentcdodds/react-testing-library/issues/1), -but if you already have a codebase that uses a different attribute for this -purpose, you can use the `configure` function of `dom-testing-library` to change -the attribute that is used. This requires `dom-testing-library` version 3.13: - -```javascript -import {configure} from 'dom-testing-library' -configure({testIdAttribute: 'data-test-id'}) -``` - -
- -#### `asFragment(): DocumentFragment` - -Returns a `DocumentFragment` of your rendered component. This can be useful if -you need to avoid live bindings and see how your component reacts to events. - -```javascript -import {render, fireEvent} from 'react-testing-library' - -class TestComponent extends React.Component { - constructor() { - super() - this.state = {count: 0} - } - - render() { - const {count} = this.state - - return ( - - ) - } -} - -const {getByText, asFragment} = render() -const firstRender = asFragment() - -fireEvent.click(getByText(/Click to increase/)) - -// This will snapshot only the difference between the first render, and the -// state of the DOM after the click event. -// See https://github.com/jest-community/snapshot-diff -expect(firstRender).toMatchDiffSnapshot(asFragment()) -``` - -### `cleanup` - -Unmounts React trees that were mounted with [render](#render). - -```javascript -import {cleanup, render} from 'react-testing-library' - -afterEach(cleanup) // <-- add this - -test('renders into document', () => { - render(
) - // ... -}) - -// ... more tests ... -``` - -Failing to call `cleanup` when you've called `render` could result in a memory -leak and tests which are not "idempotent" (which can lead to difficult to debug -errors in your tests). - -**If you don't want to add this to _every single test file_** then we recommend -that you configure your test framework to run a file before your tests which -does this automatically. See the [setup](#setup) section for guidance on how to -set up your framework. - -### `flushEffects` (experimental) - -This experimental API is intended to be used to force React's `useEffect` hook -to run synchronously. +> [**Docs**](https://react-testing-library-docs.netlify.com/docs/react-testing-library/intro) -## `dom-testing-library` APIs - -`react-testing-library` is built on top of -[`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library) and -re-exports everything from `dom-testing-library`. Some notable included exports: - -### `fireEvent(node: HTMLElement, event: Event)` - -Fire DOM events. - -React attaches an event handler on the `document` and handles some DOM events -via event delegation (events bubbling up from a `target` to an ancestor). -Because of this, your `node` must be in the `document.body` for `fireEvent` to -work with React. This is why `render` appends your container to `document.body`. -This is an alternative to simulating Synthetic React Events via -[`Simulate`](https://reactjs.org/docs/test-utils.html#simulate). The benefit of -using `fireEvent` over `Simulate` is that you are testing real DOM events -instead of Synthetic Events. This aligns better with -[the Guiding Principles](#guiding-principles). -([Also Dan Abramov told me to stop use Simulate](https://twitter.com/dan_abramov/status/980807288444383232)). - -> NOTE: If you don't like having to use `cleanup` (which we have to do because -> we render into `document.body`) to get `fireEvent` working, then feel free to -> try to chip into making it possible for React to attach event handlers to the -> rendered node rather than the `document`. Learn more here: -> [facebook/react#2043](https://github.com/facebook/react/issues/2043) - -```javascript -import {render, cleanup, fireEvent} from 'react-testing-library' - -// don't forget to clean up the document.body -afterEach(cleanup) - -test('clicks submit button', () => { - const handleClick = jest.fn() - const {getByText} = render() - - fireEvent.click(getByText('Submit')) - expect(handleClick).toHaveBeenCalledTimes(1) -}) -``` - -#### `fireEvent[eventName](node: HTMLElement, eventProperties: Object)` - -Convenience methods for firing DOM events. Check out -[dom-testing-library/src/events.js](https://github.com/kentcdodds/dom-testing-library/blob/master/src/events.js) -for a full list as well as default `eventProperties`. - -```javascript -import {render, fireEvent} from 'react-testing-library' - -const {getByText} = render(
) - -// similar to the above example -// click will bubble for React to see it -const rightClick = {button: 2} -fireEvent.click(getByText('Submit'), rightClick) -// default `button` property for click events is set to `0` which is a left click. -``` - -If you want to trigger the -[`onChange`](https://reactjs.org/docs/dom-elements.html#onchange) handler of a -[controlled component](https://reactjs.org/docs/forms.html#controlled-components) -with a different `event.target.value`, sending `value` through `eventProperties` -won't work like it does with `Simulate`. You need to use `fireEvent` to fire a -`change` DOM event with `value` property set on `target` - -```javascript -import {render, fireEvent} from 'react-testing-library' - -const {getByLabelText} = render() - -const comment = getByLabelText('Comment') -fireEvent.change(comment, { - target: {value: 'Great advice, I love your posts!'}, -}) -``` - -Note that if you want to trigger `onChange` handler on a checkbox, you should -fire a `click` event instead of `change`. - -```javascript -import {render, fireEvent} from 'react-testing-library' - -const {getByLabelText} = render() - -fireEvent.click(getByLabelText('Checkbox')) -``` - -### `waitForElement` - -> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#waitforelement) - -```js -import {render, waitForElement} from 'react-testing-library' - -test('waiting for an element', async () => { - const {getByText} = render() - - await waitForElement(() => getByText('Search')) -}) -``` - -### `wait` - -> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#wait) - -It's recommended to prefer `waitForElement`, but this can be helpful on occasion - -```javascript -import 'jest-dom/extend-expect' -import {render, wait} from 'react-testing-library' - -test('can fill in the form after loaded', async () => { - const {queryByText, getByLabelText} = render() - - // wait until the callback does not throw an error. In this case, that means - // it'll wait until the element with the text that says "loading..." is gone. - await wait(() => - expect(queryByText(/loading\.\.\./i)).not.toBeInTheDocument(), - ) - getByLabelText('username').value = 'chucknorris' - // continue doing stuff -}) -``` - -### `within` - -> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#within-and-getqueriesforelement-apis) - -The queries returned from `render` are scoped to the entire page. Sometimes, -there is no guarantee that the text, placeholder, or label you want to query is -unique on the page. So you might want to explicitly tell react-render-dom to get -an element only within a particular section of the page, within is a helper -function for this case. - -Example: To get the text 'hello' only within a section called 'messages', you -could do: - -```javascript -import {render, within} from 'react-testing-library' - -// ... - -const {getByTestId} = render(/* stuff */) -const messagesSection = getByTestId('messages') -const hello = within(messagesSection).getByText('hello') -``` - -## `TextMatch` - -Several APIs accept a `TextMatch` which can be a `string`, `regex` or a -`function` which returns `true` for a match and `false` for a mismatch. - -See [dom-testing-library#textmatch][dom-testing-lib-textmatch] for options. - -Examples: - -```javascript -import {render, getByText} from 'react-testing-library' - -const {container} = render(
Hello World
) - -// WILL find the div: - -// Matching a string: -getByText(container, 'Hello World') // full string match -getByText(container, 'llo Worl', {exact: false}) // substring match -getByText(container, 'hello world', {exact: false}) // ignore case - -// Matching a regex: -getByText(container, /World/) // substring match -getByText(container, /world/i) // substring match, ignore case -getByText(container, /^hello world$/i) // full string match, ignore case -getByText(container, /Hello W?oRlD/i) // advanced regex - -// Matching with a custom function: -getByText(container, (content, element) => content.startsWith('Hello')) - -// WILL NOT find the div: - -getByText(container, 'Goodbye World') // full string does not match -getByText(container, /hello world/) // case-sensitive regex with different case -// function looking for a span when it's actually a div: -getByText(container, (content, element) => { - return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello') -}) -``` - -## `query` APIs - -Each of the `get` APIs listed in [the `render`](#render) section above have a -complimentary `query` API. The `get` APIs will throw errors if a proper node -cannot be found. This is normally the desired effect. However, if you want to -make an assertion that an element is _not_ present in the DOM, then you can use -the `query` API instead: - -```javascript -import {render} from 'react-testing-library' - -const {queryByText} = render() -const submitButton = queryByText('submit') -expect(submitButton).toBeNull() // it doesn't exist -``` - -## `queryAll` and `getAll` APIs - -Each of the `query` APIs have a corresponding `queryAll` version that always -returns an Array of matching nodes. `getAll` is the same but throws when the -array has a length of 0. - -```javascript -import {render} from 'react-testing-library' - -const {queryAllByText} = render() -const submitButtons = queryAllByText('submit') -expect(submitButtons).toHaveLength(3) // expect 3 elements -expect(submitButtons[0]).toBeInTheDocument() -``` ## Examples -> We're in the process of moving examples to -> [`react-testing-library-examples`](https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples). +> We're in the process of moving examples to the +> [docs site](https://react-testing-library-docs.netlify.com/docs/example-codesandbox) -You'll find examples of testing with different libraries in +You'll find runnable examples of testing with different libraries in [the `examples` directory](https://github.com/kentcdodds/react-testing-library/blob/master/examples). Some included are: @@ -948,358 +156,6 @@ Some included are: You can also find react-testing-library examples at [react-testing-examples.com](https://react-testing-examples.com/jest-rtl/). -## Learning Material - -- [Migrating from Enzyme shallow rendering to explicit component mocks](https://www.youtube.com/watch?v=LHUdxkThTM0&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u) - -- [Confident React](https://www.youtube.com/watch?v=qXRPHRgcXJ0&list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf) -- [Test Driven Development with react-testing-library](https://www.youtube.com/watch?v=kCR3JAR7CHE&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u) -- [Testing React and Web Applications](https://kentcdodds.com/workshops/#testing-react-and-web-applications) -- [Build a joke app with TDD](https://medium.com/@mbaranovski/quick-guide-to-tdd-in-react-81888be67c64) - by [@mbaranovski](https://github.com/mbaranovski) -- [Build a comment feed with TDD](https://medium.freecodecamp.org/how-to-build-sturdy-react-apps-with-tdd-and-the-react-testing-library-47ad3c5c8e47) - by [@iwilsonq](https://github.com/iwilsonq) -- [A clear way to unit testing React JS components using Jest and react-testing-library](https://www.richardkotze.com/coding/react-testing-library-jest) - by [Richard Kotze](https://github.com/rkotze) - -- [Intro to react-testing-library](https://chrisnoring.gitbooks.io/react/content/testing/react-testing-library.html) - by [Chris Noring](https://github.com/softchris) -- [Integration testing in React](https://medium.com/@jeffreyrussom/integration-testing-in-react-21f92a55a894) - by [Jeffrey Russom](https://github.com/qswitcher) - -- [React-testing-library have fantastic testing 🐐](https://medium.com/yazanaabed/react-testing-library-have-a-fantastic-testing-198b04699237) - by [Yazan Aabed](https://github.com/YazanAabeed) - -- [Building a React Tooltip Library](https://www.youtube.com/playlist?list=PLMV09mSPNaQmFLPyrfFtpUdClVfutjF5G) - by [divyanshu013](https://github.com/divyanshu013) and - [metagrover](https://github.com/metagrover) - -- [A sample repo using react-testing-library to test a Relay Modern GraphQL app](https://github.com/zth/relay-modern-flow-jest-example) - -- [Creating Readable Tests Using React Testing Library](https://medium.com/flatiron-labs/creating-readable-tests-using-react-testing-library-2bd03c49c284) - by [Lukeghenco](https://github.com/Lukeghenco) -- [Course material with many examples using react-testing-library](https://github.com/kentcdodds/react-testing-library-course) - by [Kent C. Dodds](https://github.com/kentcdodds) - -Feel free to contribute more! - -## FAQ - -
- -How do I test input onChange handlers? - -TL;DR: -[Go to the `on-change.js` example](https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?module=%2Fsrc%2F__tests__%2Fon-change.js&previewwindow=tests) - -In summary: - -```javascript -import React from 'react' -import 'react-testing-library/cleanup-after-each' -import {render, fireEvent} from 'react-testing-library' - -test('change values via the fireEvent.change method', () => { - const handleChange = jest.fn() - const {container} = render() - const input = container.firstChild - fireEvent.change(input, {target: {value: 'a'}}) - expect(handleChange).toHaveBeenCalledTimes(1) - expect(input.value).toBe('a') -}) - -test('checkboxes (and radios) must use fireEvent.click', () => { - const handleChange = jest.fn() - const {container} = render() - const checkbox = container.firstChild - fireEvent.click(checkbox) - expect(handleChange).toHaveBeenCalledTimes(1) - expect(checkbox.checked).toBe(true) -}) -``` - -If you've used enzyme or React's TestUtils, you may be accustomed to changing -inputs like so: - -```javascript -input.value = 'a' -Simulate.change(input) -``` - -We can't do this with react-testing-library because React actually keeps track -of any time you assign the `value` property on an `input` and so when you fire -the `change` event, React thinks that the value hasn't actually been changed. - -This works for Simulate because they use internal APIs to fire special simulated -events. With react-testing-library, we try to avoid implementation details to -make your tests more resiliant. - -So we have it worked out for the change event handler to set the property for -you in a way that's not trackable by React. This is why you must pass the value -as part of the `change` method call. - -
- -
- -Which get method should I use? - -Based on [the Guiding Principles](#guiding-principles), your test should -resemble how your code (component, page, etc.) is used as much as possible. With -this in mind, we recommend this order of priority: - -1. `getByLabelText`: Only really good for form fields, but this is the number 1 - method a user finds those elements, so it should be your top preference. -2. `getByPlaceholderText`: - [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). - But if that's all you have, then it's better than alternatives. -3. `getByText`: Not useful for forms, but this is the number 1 method a user - finds other elements (like buttons to click), so it should be your top - preference for non-form elements. -4. `getByAltText`: If your element is one which supports `alt` text (`img`, - `area`, and `input`), then you can use this to find that element. -5. `getByTestId`: The user cannot see (or hear) these, so this is only - recommended for cases where you can't match by text or it doesn't make sense - (the text is dynamic). - -Other than that, you can also use the `container` to query the rendered -component as well (using the regular -[`querySelector` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)). - -
- -
- -Can I write unit tests with this library? - -Definitely yes! You can write unit and integration tests with this library. See -below for more on how to mock dependencies (because this library intentionally -does NOT support shallow rendering) if you want to unit test a high level -component. The tests in this project show several examples of unit testing with -this library. - -As you write your tests, keep in mind: - -> The more your tests resemble the way your software is used, the more -> confidence they can give you. - [17 Feb 2018][guiding-principle] - -
- -
- -What if my app is localized and I don't have access to the text in test? - -This is fairly common. Our first bit of advice is to try to get the default text -used in your tests. That will make everything much easier (more than just using -this utility). If that's not possible, then you're probably best to just stick -with `data-testid`s (which is not bad anyway). - -
- -
- -If I can't use shallow rendering, how do I mock out components in tests? - -In general, you should avoid mocking out components (see -[the Guiding Principles section](#guiding-principles)). However if you need to, -then it's pretty trivial using -[Jest's mocking feature](https://facebook.github.io/jest/docs/en/manual-mocks.html). -One case that I've found mocking to be especially useful is for animation -libraries. I don't want my tests to wait for animations to end. - -```javascript -jest.mock('react-transition-group', () => { - const FakeTransition = jest.fn(({children}) => children) - const FakeCSSTransition = jest.fn(props => - props.in ? {props.children} : null, - ) - return {CSSTransition: FakeCSSTransition, Transition: FakeTransition} -}) - -test('you can mock things with jest.mock', () => { - const {getByTestId, queryByTestId} = render( - , - ) - expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists - // hide the message - fireEvent.click(getByTestId('toggle-message')) - // in the real world, the CSSTransition component would take some time - // before finishing the animation which would actually hide the message. - // So we've mocked it out for our tests to make it happen instantly - expect(queryByTestId('hidden-message')).toBeNull() // we just care it doesn't exist -}) -``` - -Note that because they're Jest mock functions (`jest.fn()`), you could also make -assertions on those as well if you wanted. - -[Open full test](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/mock.react-transition-group.js) -for the full example. - -This looks like more work that shallow rendering (and it is), but it gives you -more confidence so long as your mock resembles the thing you're mocking closely -enough. - -If you want to make things more like shallow rendering, then you could do -something more -[like this](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/shallow.react-transition-group.js). - -Learn more about how Jest mocks work from my blog post: -["But really, what is a JavaScript mock?"](https://blog.kentcdodds.com/but-really-what-is-a-javascript-mock-10d060966f7d) - -
- -
- -What if I want to verify that an element does NOT exist? - -You typically will get access to rendered elements using the `getByTestId` -utility. However, that function will throw an error if the element isn't found. -If you want to specifically test for the absence of an element, then you should -use the `queryByTestId` utility which will return the element if found or `null` -if not. - -```javascript -expect(queryByTestId('thing-that-does-not-exist')).toBeNull() -``` - -
- -
- -I really don't like data-testids, but none of the other queries make sense. Do I have to use a data-testid? - -Definitely not. That said, a common reason people don't like the `data-testid` -attribute is they're concerned about shipping that to production. I'd suggest -that you probably want some simple E2E tests that run in production on occasion -to make certain that things are working smoothly. In that case the `data-testid` -attributes will be very useful. Even if you don't run these in production, you -may want to run some E2E tests that run on the same code you're about to ship to -production. In that case, the `data-testid` attributes will be valuable there as -well. - -All that said, if you really don't want to ship `data-testid` attributes, then -you can use -[this simple babel plugin](https://www.npmjs.com/package/babel-plugin-react-remove-properties) -to remove them. - -If you don't want to use them at all, then you can simply use regular DOM -methods and properties to query elements off your container. - -```javascript -const firstLiInDiv = container.querySelector('div li') -const allLisInDiv = container.querySelectorAll('div li') -const rootElement = container.firstChild -``` - -
- -
- -What if I’m iterating over a list of items that I want to put the data-testid="item" attribute on. How do I distinguish them from each other? - -You can make your selector just choose the one you want by including :nth-child -in the selector. - -```javascript -const thirdLiInUl = container.querySelector('ul > li:nth-child(3)') -``` - -Or you could include the index or an ID in your attribute: - -```javascript -
  • {item.text}
  • -``` - -And then you could use the `getByTestId` utility: - -```javascript -const items = [ - /* your items */ -] -const {getByTestId} = render(/* your component with the items */) -const thirdItem = getByTestId(`item-${items[2].id}`) -``` - -
    - -
    - -What about enzyme is "bloated with complexity and features" and "encourage -poor testing practices"? - -Most of the damaging features have to do with encouraging testing implementation -details. Primarily, these are -[shallow rendering](http://airbnb.io/enzyme/docs/api/shallow.html), APIs which -allow selecting rendered elements by component constructors, and APIs which -allow you to get and interact with component instances (and their -state/properties) (most of enzyme's wrapper APIs allow this). - -The guiding principle for this library is: - -> The more your tests resemble the way your software is used, the more -> confidence they can give you. - [17 Feb 2018][guiding-principle] - -Because users can't directly interact with your app's component instances, -assert on their internal state or what components they render, or call their -internal methods, doing those things in your tests reduce the confidence they're -able to give you. - -That's not to say that there's never a use case for doing those things, so they -should be possible to accomplish, just not the default and natural way to test -react components. - -
    - -
    - -Why isn't snapshot diffing working? - -If you use the [snapshot-diff](https://github.com/jest-community/snapshot-diff) -library to save snapshot diffs, it won't work out of the box because this -library uses the DOM which is mutable. Changes don't return new objects so -snapshot-diff will think it's the same object and avoid diffing it. - -Luckily there's an easy way to make it work: clone the DOM when passing it into -snapshot-diff. It looks like this: - -```js -const firstVersion = container.cloneNode(true) -// Do some changes -snapshotDiff(firstVersion, container.cloneNode(true)) -``` - -
    - -
    - -Does this library work with React Native? - -> This is still quite experimental - please contribute with your own -> results/findings! - -The short answer is yes, but with a few caveats. It's possible to replicate a -lot of DOM functionality with -[`react-native-web`](https://github.com/necolas/react-native-web), allowing you -to use the query APIs like `getByText`. You can then add a `press` event to -`fireEvent` that simulates a mouseDown immediately followed by a mouseUp, and -call this with Touchable\* components. - -One thing this approach does _not_ support is any kind of native module -functionality (like native navigation modules). The way around this is to design -your components so that as much of the functionality you need tested is -encapsulated outside of any native module functionality. - -For a barebones example of testing a React Native component, -[see here](https://github.com/thchia/rn-testing-library-example). - -There is also a sibling project called -[react-native-testing-library](https://github.com/callstack/react-native-testing-library) -which aims to test React Native apps without mentioned tradeoffs, having the API -inspired by and mostly compatible with this library. - -
    - ## Other Solutions In preparing this project,