Skip to content
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

chore: update React examples to match with the new React guidelines #12217

Merged
merged 15 commits into from Feb 11, 2022
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,8 @@

### Chore & Maintenance

- `[docs, examples]` Update React examples to match with the new React guidelines for code examples ([#12217](https://github.com/facebook/jest/pull/12217))

### Performance

## 27.4.7
Expand Down
11 changes: 5 additions & 6 deletions docs/SnapshotTesting.md
Expand Up @@ -9,12 +9,11 @@ A typical snapshot test case renders a UI component, takes a snapshot, then comp

## Snapshot Testing with Jest

A similar approach can be taken when it comes to testing your React components. Instead of rendering the graphical UI, which would require building the entire app, you can use a test renderer to quickly generate a serializable value for your React tree. Consider this [example test](https://github.com/facebook/jest/blob/main/examples/snapshot/__tests__/link.react.test.js) for a [Link component](https://github.com/facebook/jest/blob/main/examples/snapshot/Link.react.js):
A similar approach can be taken when it comes to testing your React components. Instead of rendering the graphical UI, which would require building the entire app, you can use a test renderer to quickly generate a serializable value for your React tree. Consider this [example test](https://github.com/facebook/jest/blob/main/examples/snapshot/__tests__/link.test.js) for a [Link component](https://github.com/facebook/jest/blob/main/examples/snapshot/Link.js):
charpeni marked this conversation as resolved.
Show resolved Hide resolved

```tsx
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
import Link from '../Link';

it('renders correctly', () => {
const tree = renderer
Expand All @@ -24,7 +23,7 @@ it('renders correctly', () => {
});
```

The first time this test is run, Jest creates a [snapshot file](https://github.com/facebook/jest/blob/main/examples/snapshot/__tests__/__snapshots__/link.react.test.js.snap) that looks like this:
The first time this test is run, Jest creates a [snapshot file](https://github.com/facebook/jest/blob/main/examples/snapshot/__tests__/__snapshots__/link.test.js.snap) that looks like this:

```javascript
exports[`renders correctly 1`] = `
Expand All @@ -41,7 +40,7 @@ exports[`renders correctly 1`] = `

The snapshot artifact should be committed alongside code changes, and reviewed as part of your code review process. Jest uses [pretty-format](https://github.com/facebook/jest/tree/main/packages/pretty-format) to make snapshots human-readable during code review. On subsequent test runs, Jest will compare the rendered output with the previous snapshot. If they match, the test will pass. If they don't match, either the test runner found a bug in your code (in the `<Link>` component in this case) that should be fixed, or the implementation has changed and the snapshot needs to be updated.

> Note: The snapshot is directly scoped to the data you render – in our example the `<Link />` component with `page` prop passed to it. This implies that even if any other file has missing props (Say, `App.js`) in the `<Link />` component, it will still pass the test as the test doesn't know the usage of `<Link />` component and it's scoped only to the `Link.react.js`. Also, rendering the same component with different props in other snapshot tests will not affect the first one, as the tests don't know about each other.
> Note: The snapshot is directly scoped to the data you render – in our example the `<Link />` component with `page` prop passed to it. This implies that even if any other file has missing props (Say, `App.js`) in the `<Link />` component, it will still pass the test as the test doesn't know the usage of `<Link />` component and it's scoped only to the `Link.js`. Also, rendering the same component with different props in other snapshot tests will not affect the first one, as the tests don't know about each other.

More information on how snapshot testing works and why we built it can be found on the [release blog post](/blog/2016/07/27/jest-14). We recommend reading [this blog post](http://benmccormick.org/2016/09/19/testing-with-jest-snapshots-first-impressions/) to get a good sense of when you should use snapshot testing. We also recommend watching this [egghead video](https://egghead.io/lessons/javascript-use-jest-s-snapshot-testing-feature?pl=testing-javascript-with-jest-a36c4074) on Snapshot Testing with Jest.

Expand Down Expand Up @@ -227,7 +226,7 @@ The goal is to make it easy to review snapshots in pull requests, and fight agai

Your tests should be deterministic. Running the same tests multiple times on a component that has not changed should produce the same results every time. You're responsible for making sure your generated snapshots do not include platform specific or other non-deterministic data.

For example, if you have a [Clock](https://github.com/facebook/jest/blob/main/examples/snapshot/Clock.react.js) component that uses `Date.now()`, the snapshot generated from this component will be different every time the test case is run. In this case we can [mock the Date.now() method](MockFunctions.md) to return a consistent value every time the test is run:
For example, if you have a [Clock](https://github.com/facebook/jest/blob/main/examples/snapshot/Clock.js) component that uses `Date.now()`, the snapshot generated from this component will be different every time the test case is run. In this case we can [mock the Date.now() method](MockFunctions.md) to return a consistent value every time the test is run:

```js
Date.now = jest.fn(() => 1482363367071);
Expand Down
66 changes: 37 additions & 29 deletions docs/TutorialReact.md
Expand Up @@ -48,7 +48,10 @@ Your `package.json` should look something like this (where `<current-version>` i

```js title="babel.config.js"
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
```

Expand All @@ -58,15 +61,15 @@ module.exports = {

Let's create a [snapshot test](SnapshotTesting.md) for a Link component that renders hyperlinks:

```tsx title="Link.react.js"
import React, {useState} from 'react';
```tsx title="Link.js"
import {useState} from 'react';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

const Link = ({page, children}) => {
export default function Link({page, children}) {
const [status, setStatus] = useState(STATUS.NORMAL);

const onMouseEnter = () => {
Expand All @@ -87,35 +90,37 @@ const Link = ({page, children}) => {
{children}
</a>
);
};
}

export default Link;
```

> Note: Examples are using Function components, but Class components can be tested in the same way. See [React: Function and Class Components](https://reactjs.org/docs/components-and-props.html#function-and-class-components). **Reminders** that with Class components, we expect Jest to be used to test props and not methods directly.

Now let's use React's test renderer and Jest's snapshot feature to interact with the component and capture the rendered output and create a snapshot file:

```tsx title="Link.react.test.js"
import React from 'react';
```tsx title="Link.test.js"
import Link from '../Link';
import renderer from 'react-test-renderer';
import Link from '../Link.react';

test('Link changes the class when hovered', () => {
it('changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();

// manually trigger the callback
tree.props.onMouseEnter();
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();

// manually trigger the callback
tree.props.onMouseLeave();
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
Expand All @@ -124,33 +129,36 @@ test('Link changes the class when hovered', () => {

When you run `yarn test` or `jest`, this will produce an output file like this:

```javascript title="__tests__/__snapshots__/Link.react.test.js.snap"
exports[`Link changes the class when hovered 1`] = `
```javascript title="__tests__/__snapshots__/Link.test.js.snap"
exports[`changes the class when hovered 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`Link changes the class when hovered 2`] = `
exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`Link changes the class when hovered 3`] = `
exports[`changes the class when hovered 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}>
onMouseLeave={[Function]}
>
Facebook
</a>
`;
Expand All @@ -160,7 +168,7 @@ The next time you run the tests, the rendered output will be compared to the pre

The code for this example is available at [examples/snapshot](https://github.com/facebook/jest/tree/main/examples/snapshot).

#### Snapshot Testing with Mocks, Enzyme and React 16
#### Snapshot Testing with Mocks, Enzyme and React 16+

There's a caveat around snapshot testing when using Enzyme and React 16+. If you mock out a module using the following style:

Expand Down Expand Up @@ -205,9 +213,9 @@ You have to run `yarn add --dev @testing-library/react` to use react-testing-lib
Let's implement a checkbox which swaps between two labels:

```tsx title="CheckboxWithLabel.js"
import React, {useState} from 'react';
import {useState} from 'react';

const CheckboxWithLabel = ({labelOn, labelOff}) => {
export default function CheckboxWithLabel({labelOn, labelOff}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
Expand All @@ -220,13 +228,10 @@ const CheckboxWithLabel = ({labelOn, labelOff}) => {
{isChecked ? labelOn : labelOff}
</label>
);
};

export default CheckboxWithLabel;
}
```

```tsx title="__tests__/CheckboxWithLabel-test.js"
import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

Expand Down Expand Up @@ -256,11 +261,14 @@ You have to run `yarn add --dev enzyme` to use Enzyme. If you are using a React
Let's rewrite the test from above using Enzyme instead of react-testing-library. We use Enzyme's [shallow renderer](http://airbnb.io/enzyme/docs/api/shallow.html) in this example.

```tsx title="__tests__/CheckboxWithLabel-test.js"
import React from 'react';
import {shallow} from 'enzyme';
import Enzyme, {shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({adapter: new Adapter()});

import CheckboxWithLabel from '../CheckboxWithLabel';

test('CheckboxWithLabel changes the text after click', () => {
it('CheckboxWithLabel changes the text after click', () => {
// Render a checkbox with label in the document
const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);

Expand Down
6 changes: 4 additions & 2 deletions examples/enzyme/.babelrc.js
@@ -1,6 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties']
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
40 changes: 21 additions & 19 deletions examples/enzyme/CheckboxWithLabel.js
@@ -1,26 +1,28 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React from 'react';
import {useState} from 'react';

export default class CheckboxWithLabel extends React.Component {
state = {
isChecked: false,
};
export default function CheckboxWithLabel({
labelRef,
inputRef,
labelOn,
labelOff,
}) {
const [isChecked, setIsChecked] = useState(false);

onChange = () => {
this.setState({isChecked: !this.state.isChecked});
const onChange = () => {
setIsChecked(!isChecked);
};

render() {
return (
<label>
<input
type="checkbox"
checked={this.state.isChecked}
onChange={this.onChange}
/>
{this.state.isChecked ? this.props.labelOn : this.props.labelOff}
</label>
);
}
return (
<label ref={labelRef}>
<input
ref={inputRef}
type="checkbox"
checked={isChecked}
onChange={onChange}
/>
{isChecked ? labelOn : labelOff}
</label>
);
}
1 change: 0 additions & 1 deletion examples/enzyme/__tests__/CheckboxWithLabel-test.js
@@ -1,6 +1,5 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React from 'react';
import Enzyme, {shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Expand Down
6 changes: 4 additions & 2 deletions examples/react-testing-library/.babelrc.js
@@ -1,6 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
8 changes: 3 additions & 5 deletions examples/react-testing-library/CheckboxWithLabel.js
@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {useState} from 'react';
import {useState} from 'react';

const CheckboxWithLabel = ({labelOn, labelOff}) => {
export default function CheckboxWithLabel({labelOn, labelOff}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
Expand All @@ -15,6 +15,4 @@ const CheckboxWithLabel = ({labelOn, labelOff}) => {
{isChecked ? labelOn : labelOff}
</label>
);
};

export default CheckboxWithLabel;
}
@@ -1,6 +1,5 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React from 'react';
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

Expand Down
5 changes: 4 additions & 1 deletion examples/react/.babelrc.js
@@ -1,5 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
13 changes: 8 additions & 5 deletions examples/react/CheckboxWithLabel.js
@@ -1,8 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {useState} from 'react';
import {useState} from 'react';

const CheckboxWithLabel = ({labelRef, inputRef, labelOn, labelOff}) => {
export default function CheckboxWithLabel({
labelRef,
inputRef,
labelOn,
labelOff,
}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
Expand All @@ -20,6 +25,4 @@ const CheckboxWithLabel = ({labelRef, inputRef, labelOn, labelOff}) => {
{isChecked ? labelOn : labelOff}
</label>
);
};

export default CheckboxWithLabel;
}
2 changes: 1 addition & 1 deletion examples/react/__tests__/CheckboxWithLabel-test.js
@@ -1,6 +1,6 @@
// Copyright 2004-present Facebook. All Rights Reserved.

import React, {createRef} from 'react';
import {createRef} from 'react';

import * as TestUtils from 'react-dom/test-utils';
import CheckboxWithLabel from '../CheckboxWithLabel';
Expand Down
6 changes: 4 additions & 2 deletions examples/snapshot/.babelrc.js
@@ -1,6 +1,8 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.

module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties'],
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};