Browse Source
* Testing Docs This expands the testing docs for reactjs.org. It adds 3 main docs - - testing.md: An entry point to the testing docs - testing-recipes.md: Common patterns when writing tests for React Components. - testing-environments.md: Setting up your testing environment. With help from @alexkrolick, @kentcdodds, @gaearonmain
Sunil Pai
6 years ago
committed by
GitHub
10 changed files with 739 additions and 20 deletions
@ -1,13 +0,0 @@ |
|||
--- |
|||
id: testing |
|||
title: Testing |
|||
layout: community |
|||
permalink: community/testing.html |
|||
--- |
|||
|
|||
* **[Enzyme](https://github.com/airbnb/enzyme/):** a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output. |
|||
* **[Jest](https://facebook.github.io/jest/):** Delightful JavaScript testing used by Facebook to test all JavaScript code including React applications. |
|||
* **[react-testing-library](https://github.com/kentcdodds/react-testing-library):** 🐐 Simple and complete React DOM testing utilities that encourage good testing practices. |
|||
* **[React-unit](https://github.com/pzavolinsky/react-unit):** a lightweight unit test library for ReactJS with very few (js-only) dependencies. |
|||
* **[Skin-deep](https://github.com/glenjamin/skin-deep):** Testing helpers for use with React's shallowRender test utils. |
|||
* **[Unexpected-react](https://github.com/bruderstein/unexpected-react/):** Plugin for the [unexpected](https://unexpected.js.org/) assertion library that makes it easy to assert over your React Components and trigger events. |
@ -0,0 +1,58 @@ |
|||
--- |
|||
id: testing-environments |
|||
title: Testing Environments |
|||
permalink: docs/testing-environments.html |
|||
prev: testing-recipes.html |
|||
--- |
|||
|
|||
<!-- This document is intended for folks who are comfortable with JavaScript, and have probably written tests with it. It acts as a reference for the differences in testing environments for React components, and how those differences affect the tests that they write. This document also assumes a slant towards web-based react-dom components, but has notes for other renderers. --> |
|||
|
|||
This document goes through the factors that can affect your environment and recommendations for some scenarios. |
|||
|
|||
### Test runners {#test-runners} |
|||
|
|||
Test runners like [Jest](https://jestjs.io/), [mocha](https://mochajs.org/), [ava](https://github.com/avajs/ava) let you write test suites as regular JavaScript, and run them as part of your development process. Additionally, test suites are run as part of continuous integration. |
|||
|
|||
- Jest is widely compatible with React projects, supporting features like mocked [modules](#mocking-modules) and [timers](#mocking-timers), and [`jsdom`](#mocking-a-rendering-surface) support. **If you use Create React App, [Jest is already included out of the box](https://facebook.github.io/create-react-app/docs/running-tests) with useful defaults.** |
|||
- Libraries like [mocha](https://mochajs.org/#running-mocha-in-the-browser) work well in real browser environments, and could help for tests that explicitly need it. |
|||
- End-to-end tests are used for testing longer flows across multiple pages, and require a [different setup](#end-to-end-tests-aka-e2e-tests). |
|||
|
|||
### Mocking a rendering surface {#mocking-a-rendering-surface} |
|||
|
|||
Tests often run in an environment without access to a real rendering surface like a browser. For these environments, we recommend simulating a browser with [`jsdom`](https://github.com/jsdom/jsdom), a lightweight browser implementation that runs inside Node.js. |
|||
|
|||
In most cases, jsdom behaves like a regular browser would, but doesn't have features like [layout and navigation](https://github.com/jsdom/jsdom#unimplemented-parts-of-the-web-platform). This is still useful for most web-based component tests, since it runs quicker than having to start up a browser for each test. It also runs in the same process as your tests, so you can write code to examine and assert on the rendered DOM. |
|||
|
|||
Just like in a real browser, jsdom lets us model user interactions; tests can dispatch events on DOM nodes, and then observe and assert on the side effects of these actions [<small>(example)</small>](/docs/testing-recipes.html#events). |
|||
|
|||
A large portion of UI tests can be written with the above setup: using Jest as a test runner, rendered to jsdom, with user interactions specified as sequences of browser events, powered by the `act()` helper [<small>(example)</small>](/docs/testing-recipes.html). For example, a lot of React's own tests are written with this combination. |
|||
|
|||
If you're writing a library that tests mostly browser-specific behavior, and requires native browser behavior like layout or real inputs, you could use a framework like [mocha.](https://mochajs.org/) |
|||
|
|||
In an environment where you _can't_ simulate a DOM (e.g. testing React Native components on Node.js), you could use [event simulation helpers](https://reactjs.org/docs/test-utils.html#simulate) to simulate interactions with elements. Alternately, you could use the `fireEvent` helper from [`@testing-library/react-native`](https://testing-library.com/docs/native-testing-library). |
|||
|
|||
Frameworks like [Cypress](https://www.cypress.io/), [puppeteer](https://github.com/GoogleChrome/puppeteer) and [webdriver](https://www.seleniumhq.org/projects/webdriver/) are useful for running [end-to-end tests](#end-to-end-tests-aka-e2e-tests). |
|||
|
|||
### Mocking functions {#mocking-functions} |
|||
|
|||
When writing tests, we'd like to mock out the parts of our code that don't have equivalents inside our testing environment (e.g. checking `navigator.onLine` status inside Node.js). Tests could also spy on some functions, and observe how other parts of the test interact with them. It is then useful to be able to selectively mock these functions with test-friendly versions. |
|||
|
|||
This is especially useful for data fetching. It is usually preferable to use "fake" data for tests to avoid the slowness and flakiness due to fetching from real API endpoints [<small>(example)</small>](/docs/testing-recipes.html#data-fetching). This helps make the tests predictable. Libraries like [Jest](https://jestjs.io/) and [sinon](https://sinonjs.org/), among others, support mocked functions. For end-to-end tests, mocking network can be more difficult, but you might also want to test the real API endpoints in them anyway. |
|||
|
|||
### Mocking modules {#mocking-modules} |
|||
|
|||
Some components have dependencies for modules that may not work well in test environments, or aren't essential to our tests. It can be useful to selectively mock these modules out with suitable replacements [<small>(example)</small>](/docs/testing-recipes.html#mocking-modules). |
|||
|
|||
On Node.js, runners like Jest [support mocking modules](https://jestjs.io/docs/en/manual-mocks). You could also use libraries like [`mock-require`](https://www.npmjs.com/package/mock-require). |
|||
|
|||
### Mocking timers {#mocking-timers} |
|||
|
|||
Components might be using time-based functions like `setTimeout`, `setInterval`, or `Date.now`. In testing environments, it can be helpful to mock these functions out with replacements that let you manually "advance" time. This is great for making sure your tests run fast! Tests that are dependent on timers would still resolve in order, but quicker [<small>(example)</small>](/docs/testing-recipes.html#timers). Most frameworks, including [Jest](https://jestjs.io/docs/en/timer-mocks), [sinon](https://sinonjs.org/releases/v7.3.2/fake-timers/) and [lolex](https://github.com/sinonjs/lolex), let you mock timers in your tests. |
|||
|
|||
Sometimes, you may not want to mock timers. For example, maybe you're testing an animation, or interacting with an endpoint that's sensitive to timing (like an API rate limiter). Libraries with timer mocks let you enable and disable them on a per test/suite basis, so you can explicitly choose how these tests would run. |
|||
|
|||
### End-to-end tests {#end-to-end-tests-aka-e2e-tests} |
|||
|
|||
End-to-end tests are useful for testing longer workflows, especially when they're critical to your business (such as payments or signups). For these tests, you'd probably want to test both how a real browser renders the whole app, fetches data from the real API endpoints, uses sessions and cookies, navigates between different links. You might also likely want to make assertions not just on the DOM state, but on the backing data as well (e.g. to verify whether the updates have been persisted to the database). |
|||
|
|||
In this scenario, you would use a framework like [Cypress](https://www.cypress.io/) or a library like [puppeteer](https://github.com/GoogleChrome/puppeteer) so you can navigate between multiple routes and assert on side effects not just in the browser, but potentially on the backend as well. |
@ -0,0 +1,595 @@ |
|||
--- |
|||
id: testing-recipes |
|||
title: Testing Recipes |
|||
permalink: docs/testing-recipes.html |
|||
prev: testing.html |
|||
next: testing-environments.html |
|||
--- |
|||
|
|||
Common testing patterns for React components. |
|||
|
|||
> Note: |
|||
> |
|||
> This page assumes you're using [Jest](https://jestjs.io/) as a test runner. If you use a different test runner, you may need to adjust the API, but the overall shape of the solution will likely be the same. Read more details on setting up a testing environment on the [Testing Environments](/docs/testing-environments.html) page. |
|||
|
|||
On this page, we will primarily use function components. However, these testing strategies don't depend on implementation details, and work just as well for class components too. |
|||
|
|||
### Setup / Teardown {#setup--teardown} |
|||
|
|||
For each test, we usually want to render our React tree to a DOM element that's attached to `document`. This is important so that it can receive DOM events. When the test ends, we want to "clean up" and unmount the tree from the `document`. |
|||
|
|||
A common way to do it is to use a pair of `beforeEach` and `afterEach` blocks so that they'll always run and isolate the effects of a test to itself: |
|||
|
|||
```jsx |
|||
import { unmountComponentAtNode } from "react-dom"; |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
``` |
|||
|
|||
You may use a different pattern, but keep in mind that we want to execute the cleanup _even if a test fails_. Otherwise, tests can become "leaky", and one test can change the behavior of another test. That makes them difficult to debug. |
|||
|
|||
### `act()` {#act} |
|||
|
|||
When writing UI tests, tasks like rendering, user events, or data fetching can be considered as "units" of interaction with a user interface. React provides a helper called `act()` that makes sure all updates related to these "units" have been processed and applied to the DOM before you make any assertions: |
|||
|
|||
```js |
|||
act(() => { |
|||
// render components |
|||
}); |
|||
// make assertions |
|||
``` |
|||
|
|||
This helps make your tests run closer to what real users would experience when using your application. The rest of these examples use `act()` to make these guarantees. |
|||
|
|||
You might find using `act()` directly a bit too verbose. To avoid some of the boilerplate, you could use a library like [React Testing Library](https://testing-library.com/react), whose helpers are wrapped with `act()`. |
|||
|
|||
> Note: |
|||
> |
|||
> The name `act` comes from the [Arrange-Act-Assert](http://wiki.c2.com/?ArrangeActAssert) pattern. |
|||
|
|||
### Rendering {#rendering} |
|||
|
|||
Commonly, you might want to test whether a component renders correctly for given props. Consider a simple component that renders a message based on a prop: |
|||
|
|||
```jsx |
|||
// hello.js |
|||
|
|||
import React from "react"; |
|||
|
|||
export default function Hello(props) { |
|||
if (props.name) { |
|||
return <h1>Hello, {props.name}!</h1>; |
|||
} else { |
|||
return <span>Hey, stranger</span>; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
We can write a test for this component: |
|||
|
|||
```jsx{24-27} |
|||
// hello.test.js |
|||
|
|||
import React from "react"; |
|||
import { render, unmountComponentAtNode } from "react-dom"; |
|||
import { act } from "react-dom/test-utils"; |
|||
|
|||
import Hello from "./hello"; |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
|
|||
it("renders with or without a name", () => { |
|||
act(() => { |
|||
render(<Hello />, container); |
|||
}); |
|||
expect(container.textContent).toBe("Hey, stranger"); |
|||
|
|||
act(() => { |
|||
render(<Hello name="Jenny" />, container); |
|||
}); |
|||
expect(container.textContent).toBe("Hello, Jenny!"); |
|||
|
|||
act(() => { |
|||
render(<Hello name="Margaret" />, container); |
|||
}); |
|||
expect(container.textContent).toBe("Hello, Margaret!"); |
|||
}); |
|||
``` |
|||
|
|||
### Data fetching {#data-fetching} |
|||
|
|||
Instead of calling real APIs in all your tests, you can mock requests with dummy data. Mocking data fetching with "fake" data prevents flaky tests due to an unavailable backend, and makes them run faster. Note: you may still want to run a subset of tests using an ["end-to-end"](/docs/testing-environments.html#end-to-end-tests-aka-e2e-tests) framework that tells whether the whole app is working together. |
|||
|
|||
```jsx |
|||
// user.js |
|||
|
|||
import React, { useState, useEffect } from "react"; |
|||
|
|||
export default function User(props) { |
|||
const [user, setUser] = useState(null); |
|||
|
|||
async function fetchUserData(id) { |
|||
const response = await fetch("/" + id); |
|||
setUser(await response.json()); |
|||
} |
|||
|
|||
useEffect(() => { |
|||
fetchUserData(props.id); |
|||
}, [props.id]); |
|||
|
|||
if (!user) { |
|||
return "loading..."; |
|||
} |
|||
|
|||
return ( |
|||
<details> |
|||
<summary>{user.name}</summary> |
|||
<strong>{user.age}</strong> years old |
|||
<br /> |
|||
lives in {user.address} |
|||
</details> |
|||
); |
|||
} |
|||
``` |
|||
|
|||
We can write tests for it: |
|||
|
|||
```jsx{23-33,44-45} |
|||
// user.test.js |
|||
|
|||
import React from "react"; |
|||
import { render, unmountComponentAtNode } from "react-dom"; |
|||
import { act } from "react-dom/test-utils"; |
|||
import User from "./user"; |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
|
|||
it("renders user data", async () => { |
|||
const fakeUser = { |
|||
name: "Joni Baez", |
|||
age: "32", |
|||
address: "123, Charming Avenue" |
|||
}; |
|||
|
|||
jest.spyOn(global, "fetch").mockImplementation(() => |
|||
Promise.resolve({ |
|||
json: () => Promise.resolve(fakeUser) |
|||
}) |
|||
); |
|||
|
|||
// Use the asynchronous version of act to apply resolved promises |
|||
await act(async () => { |
|||
render(<User id="123" />, container); |
|||
}); |
|||
|
|||
expect(container.querySelector("summary").textContent).toBe(fakeUser.name); |
|||
expect(container.querySelector("strong").textContent).toBe(fakeUser.age); |
|||
expect(container.textContent).toContain(fakeUser.address); |
|||
|
|||
// remove the mock to ensure tests are completely isolated |
|||
global.fetch.mockRestore(); |
|||
}); |
|||
``` |
|||
|
|||
### Mocking modules {#mocking-modules} |
|||
|
|||
Some modules might not work well inside a testing environment, or may not be as essential to the test itself. Mocking out these modules with dummy replacements can make it easier to write tests for your own code. |
|||
|
|||
Consider a `Contact` component that embeds a third-party `GoogleMap` component: |
|||
|
|||
```jsx |
|||
// map.js |
|||
|
|||
import React from "react"; |
|||
|
|||
import { LoadScript, GoogleMap } from "react-google-maps"; |
|||
export default function Map(props) { |
|||
return ( |
|||
<LoadScript id="script-loader" googleMapsApiKey="YOUR_API_KEY"> |
|||
<GoogleMap id="example-map" center={props.center} /> |
|||
</LoadScript> |
|||
); |
|||
} |
|||
|
|||
// contact.js |
|||
|
|||
import React from "react"; |
|||
import Map from "./map"; |
|||
|
|||
function Contact(props) { |
|||
return ( |
|||
<div> |
|||
<address> |
|||
Contact {props.name} via{" "} |
|||
<a data-test-id="email" href={"mailto:" + props.email}> |
|||
email |
|||
</a> |
|||
or on their <a data-test-id="site" href={props.site}> |
|||
website |
|||
</a>. |
|||
</address> |
|||
<Map center={props.center} /> |
|||
</div> |
|||
); |
|||
} |
|||
``` |
|||
|
|||
If we don't want to load this component in our tests, we can mock out the dependency itself to a dummy component, and run our tests: |
|||
|
|||
```jsx{10-18} |
|||
// contact.test.js |
|||
|
|||
import React from "react"; |
|||
import { render, unmountComponentAtNode } from "react-dom"; |
|||
import { act } from "react-dom/test-utils"; |
|||
|
|||
import Contact from "./contact"; |
|||
import MockedMap from "./map"; |
|||
|
|||
jest.mock("./map", () => { |
|||
return function DummyMap(props) { |
|||
return ( |
|||
<div data-test-id="map"> |
|||
{props.center.lat}:{props.center.long} |
|||
</div> |
|||
); |
|||
}; |
|||
}); |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
|
|||
it("should render contact information", () => { |
|||
const center = { lat: 0, long: 0 }; |
|||
act(() => { |
|||
render( |
|||
<Contact |
|||
name="Joni Baez" |
|||
email="test@example.com" |
|||
site="http://test.com" |
|||
center={center} |
|||
/>, |
|||
container |
|||
); |
|||
}); |
|||
|
|||
expect( |
|||
container.querySelector("[data-test-id='email']").getAttribute("href") |
|||
).toEqual("mailto:test@example.com"); |
|||
|
|||
expect( |
|||
container.querySelector('[data-test-id="site"]').getAttribute("href") |
|||
).toEqual("http://test.com"); |
|||
|
|||
expect(container.querySelector('[data-test-id="map"]').textContent).toEqual( |
|||
"0:0" |
|||
); |
|||
}); |
|||
``` |
|||
|
|||
### Events {#events} |
|||
|
|||
We recommend dispatching real DOM events on DOM elements, and then asserting on the result. Consider a `Toggle` component: |
|||
|
|||
```jsx |
|||
// toggle.js |
|||
|
|||
import React, { useState } from "react"; |
|||
|
|||
export default function Toggle(props) { |
|||
const [state, setState] = useState(false); |
|||
return ( |
|||
<button |
|||
onClick={() => { |
|||
setState(previousState => !previousState); |
|||
props.onChange(!state); |
|||
}} |
|||
data-testid="toggle" |
|||
> |
|||
{state === true ? "Turn off" : "Turn on"} |
|||
</button> |
|||
); |
|||
} |
|||
``` |
|||
|
|||
We could write tests for it: |
|||
|
|||
```jsx{13-14,35,43} |
|||
// toggle.test.js |
|||
|
|||
import React from "react"; |
|||
import { render, unmountComponentAtNode } from "react-dom"; |
|||
import { act } from "react-dom/test-utils"; |
|||
|
|||
import Toggle from "./toggle"; |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
// container *must* be attached to document so events work correctly. |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
|
|||
it("changes value when clicked", () => { |
|||
const onChange = jest.fn(); |
|||
act(() => { |
|||
render(<Toggle onChange={onChange} />, container); |
|||
}); |
|||
|
|||
// get a hold of the button element, and trigger some clicks on it |
|||
const button = document.querySelector("[data-testid=toggle]"); |
|||
expect(button.innerHTML).toBe("Turn off"); |
|||
|
|||
act(() => { |
|||
button.dispatchEvent(new MouseEvent("click", { bubbles: true })); |
|||
}); |
|||
|
|||
expect(onChange).toHaveBeenCalledTimes(1); |
|||
expect(button.innerHTML).toBe("Turn on"); |
|||
|
|||
act(() => { |
|||
for (let i = 0; i < 5; i++) { |
|||
button.dispatchEvent(new MouseEvent("click", { bubbles: true })); |
|||
} |
|||
}); |
|||
|
|||
expect(onChange).toHaveBeenCalledTimes(6); |
|||
expect(button.innerHTML).toBe("Turn on!"); |
|||
}); |
|||
``` |
|||
|
|||
Diffrent DOM events and their properties are described in [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent). Note that you need to pass `{ bubbles: true }` in each event you create for it to reach the React listener because React automatically delegates events to the document. |
|||
|
|||
> Note: |
|||
> |
|||
> React Testing Library offers a [more concise helper](https://testing-library.com/docs/dom-testing-library/api-events) for firing events. |
|||
|
|||
### Timers {#timers} |
|||
|
|||
Your code might use timer-based functions like `setTimeout` to schedule more work in the future. In this example, a multiple choice panel waits for a selection and advances, timing out if a selection isn't made in 5 seconds: |
|||
|
|||
```jsx |
|||
//card.js |
|||
|
|||
import React, { useEffect } from "react"; |
|||
|
|||
export default function Card(props) { |
|||
useEffect(() => { |
|||
const timeoutID = setTimeout(() => { |
|||
props.onSelect(null); |
|||
}, 500); |
|||
return () => { |
|||
clearTimeout(timeoutID); |
|||
}; |
|||
}, [props.onSelect]); |
|||
|
|||
return [1, 2, 3, 4].map(choice => ( |
|||
<button |
|||
key={choice} |
|||
data-test-id={choice} |
|||
onClick={() => props.onSelect(choice)} |
|||
> |
|||
{choice} |
|||
</button> |
|||
)); |
|||
} |
|||
``` |
|||
|
|||
We can write tests for this component by leveraging [Jest's timer mocks](https://jestjs.io/docs/en/timer-mocks), and testing the different states it can be in. |
|||
|
|||
```jsx{7,31,37,49,59} |
|||
// card.test.js |
|||
|
|||
import React from "react"; |
|||
import { render, unmountComponentAtNode } from "react-dom"; |
|||
import { act } from "react-dom/test-utils"; |
|||
|
|||
jest.useFakeTimers(); |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
|
|||
it("should select null after timing out", () => { |
|||
const onSelect = jest.fn(); |
|||
act(() => { |
|||
render(<Card onSelect={onSelect} />, container); |
|||
}); |
|||
|
|||
// move ahead in time by 100ms |
|||
act(() => { |
|||
jest.advanceTimersByTime(100); |
|||
}); |
|||
expect(onSelect).not.toHaveBeenCalled(); |
|||
|
|||
// and then move ahead by 1 second |
|||
act(() => { |
|||
jest.advanceTimersByTime(1000); |
|||
}); |
|||
expect(onSelect).toHaveBeenCalledWith(null); |
|||
}); |
|||
|
|||
it("should cleanup on being removed", () => { |
|||
const onSelect = jest.fn(); |
|||
act(() => { |
|||
render(<Card onSelect={onSelect} />, container); |
|||
}); |
|||
|
|||
act(() => { |
|||
jest.advanceTimersByTime(100); |
|||
}); |
|||
expect(onSelect).not.toHaveBeenCalled(); |
|||
|
|||
// unmount the app |
|||
act(() => { |
|||
render(null, container); |
|||
}); |
|||
|
|||
act(() => { |
|||
jest.advanceTimersByTime(1000); |
|||
}); |
|||
expect(onSelect).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("should accept selections", () => { |
|||
const onSelect = jest.fn(); |
|||
act(() => { |
|||
render(<Card onSelect={onSelect} />, container); |
|||
}); |
|||
|
|||
act(() => { |
|||
container |
|||
.querySelector("[data-test-id=2]") |
|||
.dispatchEvent(new MouseEvent("click", { bubbles: true })); |
|||
}); |
|||
|
|||
expect(onSelect).toHaveBeenCalledWith(2); |
|||
}); |
|||
``` |
|||
|
|||
You can use fake timers only in some tests. Above, we enabled them by calling `jest.useFakeTimers()`. The main advantage they provide is that your test doesn't actually have to wait five seconds to execute, and you also didn't need to make the component code more convoluted just for testing. |
|||
|
|||
### Snapshot testing {#snapshot-testing} |
|||
|
|||
Frameworks like Jest also let you save "snapshots" of data with [`toMatchSnapshot` / `toMatchInlineSnapshot`](https://jestjs.io/docs/en/snapshot-testing). With these, we can "save" the renderered component output and ensure that a change to it has to be explicitly committed as a change to the snapshot. |
|||
|
|||
In this example, we render a component and format the rendered HTML with the [`pretty`](https://www.npmjs.com/package/pretty) package, before saving it as an inline snapshot: |
|||
|
|||
```jsx{29-31} |
|||
// hello.test.js, again |
|||
|
|||
import React from "react"; |
|||
import { render, unmountComponentAtNode } from "react-dom"; |
|||
import { act } from "react-dom/test-utils"; |
|||
import pretty from "pretty"; |
|||
|
|||
import Hello from "./hello"; |
|||
|
|||
let container = null; |
|||
beforeEach(() => { |
|||
// setup a DOM element as a render target |
|||
container = document.createElement("div"); |
|||
document.body.appendChild(container); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
// cleanup on exiting |
|||
unmountComponentAtNode(container); |
|||
container.remove(); |
|||
container = null; |
|||
}); |
|||
|
|||
it("should render a greeting", () => { |
|||
act(() => { |
|||
render(<Hello />, container); |
|||
}); |
|||
|
|||
expect( |
|||
pretty(container.innerHTML) |
|||
).toMatchInlineSnapshot(); /* ... gets filled automatically by jest ... */ |
|||
|
|||
act(() => { |
|||
render(<Hello name="Jenny" />, container); |
|||
}); |
|||
|
|||
expect( |
|||
pretty(container.innerHTML) |
|||
).toMatchInlineSnapshot(); /* ... gets filled automatically by jest ... */ |
|||
|
|||
act(() => { |
|||
render(<Hello name="Margaret" />, container); |
|||
}); |
|||
|
|||
expect( |
|||
pretty(container.innerHTML) |
|||
).toMatchInlineSnapshot(); /* ... gets filled automatically by jest ... */ |
|||
}); |
|||
``` |
|||
|
|||
It's typically better to make more specific assertions than to use snapshots. These kinds of tests include implementation details so they break easily, and teams can get desensitized to snapshot breakages. Selectively [mocking some child components](#mocking-modules) can help reduce the size of snapshots and keep them readable for the code review. |
|||
|
|||
### Multiple renderers {#multiple-renderers} |
|||
|
|||
In rare cases, you may be running a test on a component that uses multiple renderers. For example, you may be running snapshot tests on a component with `react-test-renderer`, that internally uses `ReactDOM.render` inside a child component to render some content. In this scenario, you can wrap updates with `act()`s corresponding to their renderers. |
|||
|
|||
```jsx |
|||
import { act as domAct } from "react-dom/test-utils"; |
|||
import { act as testAct, create } from "react-test-renderer"; |
|||
// ... |
|||
let root; |
|||
domAct(() => { |
|||
testAct(() => { |
|||
root = create(<App />); |
|||
}); |
|||
}); |
|||
expect(root).toMatchSnapshot(); |
|||
``` |
|||
|
|||
### Something missing? {#something-missing} |
|||
|
|||
If some common scenario is not covered, please let us know on the [issue tracker](https://github.com/reactjs/reactjs.org/issues) for the documentation website. |
@ -0,0 +1,40 @@ |
|||
--- |
|||
id: testing |
|||
title: Testing Overview |
|||
permalink: docs/testing.html |
|||
redirect_from: |
|||
- "community/testing.html" |
|||
next: testing-recipes.html |
|||
--- |
|||
|
|||
You can test React components similar to testing other JavaScript code. |
|||
|
|||
There are a few ways to test React components. Broadly, they divide into two categories: |
|||
|
|||
* **Rendering component trees** in a simplified test environment and asserting on their output. |
|||
* **Running a complete app** in a realistic browser environment (also known as “end-to-end” tests). |
|||
|
|||
This documentation section focuses on testing strategies for the first case. While full end-to-end tests can be very useful to prevent regressions to important workflows, such tests are not concerned with React components in particular, and are out of scope of this section. |
|||
|
|||
### Tradeoffs {#tradeoffs} |
|||
|
|||
|
|||
When choosing testing tools, it is worth considering a few tradeoffs: |
|||
|
|||
* **Iteration speed vs Realistic environment:** Some tools offer a very quick feedback loop between making a change and seeing the result, but don't model the browser behavior precisely. Other tools might use a real browser environment, but reduce the iteration speed and are flakier on a continuous integration server. |
|||
* **How much to mock:** With components, the distinction between a "unit" and "integration" test can be blurry. If you're testing a form, should its test also test the buttons inside of it? Or should a button component have its own test suite? Should refactoring a button ever break the form test? |
|||
|
|||
Different answers may work for different teams and products. |
|||
|
|||
### Recommended Tools {#tools} |
|||
|
|||
**[Jest](https://facebook.github.io/jest/)** is a JavaScript test runner that lets you access the DOM via [`jsdom`](#mocking-a-rendering-surface). While jsdom is only an approximation of how the browser works, it is often good enough for testing React components. Jest provides a great iteration speed combined with powerful features like mocking [modules](#mocking-modules) and [timers](#mocking-timers) so you can have more control over how the code executes. |
|||
|
|||
**[React Testing Library](https://testing-library.com/react)** is a set of helpers that let you test React components without relying on their implementation details. This approach makes refactoring a breeze and also nudges you towards best practices for accessibility. Although it doesn't provide a way to "shallowly" render a component without its children, a test runner like Jest lets you do this by [mocking](/docs/testing-recipes.html#mocking-modules). |
|||
|
|||
### Learn more {#learn-more} |
|||
|
|||
This section is divided in two pages: |
|||
|
|||
- [Recipes](/docs/testing-recipes.html): Common patterns when writing tests for React components. |
|||
- [Environments](/docs/testing-environments.html): What to consider when setting up a testing environment for React components. |
Loading…
Reference in new issue