Перейти до основного змісту
Версія: 29.7

Тестування React додатків

At Facebook, we use Jest to test React applications.

Налаштування

Налаштування з Create React App

If you are new to React, we recommend using Create React App. It is ready to use and ships with Jest! You will only need to add react-test-renderer for rendering snapshots.

Запустіть

npm install --save-dev react-test-renderer

Налаштування без Create React App

Якщо у вас вже є існуючий додаток, вам потрібно встановити кілька пакунків, щоб змусити все працювати. We are using the babel-jest package and the react babel preset to transform our code inside of the test environment. Also see using babel.

Запустіть

npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

Your package.json should look something like this (where <current-version> is the actual latest version number for the package). Додайте необхідні скрипти і конфігурацію Jest:

{
"dependencies": {
"react": "<current-version>",
"react-dom": "<current-version>"
},
"devDependencies": {
"@babel/preset-env": "<current-version>",
"@babel/preset-react": "<current-version>",
"babel-jest": "<current-version>",
"jest": "<current-version>",
"react-test-renderer": "<current-version>"
},
"scripts": {
"test": "jest"
}
}
babel.config.js
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};

And you're good to go!

Тестування з допомогою знімків

Let's create a snapshot test for a Link component that renders hyperlinks:

Link.js
import {useState} from 'react';

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

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

const onMouseEnter = () => {
setStatus(STATUS.HOVERED);
};

const onMouseLeave = () => {
setStatus(STATUS.NORMAL);
};

return (
<a
className={status}
href={page || '#'}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
}
note

Приклади використовують функціональні компоненти, проте класові компоненти можна перевірити аналогічно. See React: Function and Class Components. Reminders that with Class components, we expect Jest to be used to test props and not methods directly.

Тепер давайте використаємо тестовий рендерер React і функцію створення знімків Jest для взаємодії з компонентом, отримання результату його відображення і створення файла знімку:

Link.test.js
import renderer from 'react-test-renderer';
import Link from '../Link';

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
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();

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

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

__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]}
>
Facebook
</a>
`;

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

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

Наступного разу, під час запуску тестів, результат роботи компонента буде порівняний зі збереженим знімком. The snapshot should be committed along with code changes. Коли тест, що використовує знімки, провалиться, вам потрібно буде перевірити чи відбулися навмисні, чи ненавмисні зміни. If the change is expected you can invoke Jest with jest -u to overwrite the existing snapshot.

The code for this example is available at examples/snapshot.

Тестування знімків імітаціями, Enzyme та 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:

jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');

Then you will see warnings in the console:

Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.

# Or:
Warning: The tag <SomeComponent> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

React 16 triggers these warnings due to how it checks element types, and the mocked module fails these checks. Your options are:

  1. Рендер у вигляді тексту. Таким чином, ви не побачите реквізитів в компоненті-імітації в знімку, але це прямий підхід:
    jest.mock('./SomeComponent', () => () => 'SomeComponent');
  2. Рендер у вигляді користувацького елемента. "Користувацькі елементи" DOM нічого не перевіряють та не повинні викликати попередження. Вони мають нижній регістр і тире в імені.
    jest.mock('./Widget', () => () => <mock-widget />);
  3. Use react-test-renderer. The test renderer doesn't care about element types and will happily accept e.g. SomeComponent. Ви можете перевірити знімки, використовуючи рендер тесту, та окремо перевірити поведінку компоненту за допомогою Enzyme.
  4. Повне вимкнення застережень (має бути виконано в файлі налаштувань Jest):
    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
    Зазвичай, вам варто уникати цього варіанту, оскільки можна втратити корисні попередження. Однак, в деяких випадках, наприклад, при тестуванні компонентів react-native, ми рендеримо теги react-native всередині DOM і більшість попереджень не мають значення. Іншим варіантом є встановлення console.warn та приховування конкретних попереджень.

DOM тестування

If you'd like to assert, and manipulate your rendered components you can use @testing-library/react, Enzyme, or React's TestUtils. The following example use @testing-library/react.

@testing-library/react

npm install --save-dev @testing-library/react

Let's implement a checkbox which swaps between two labels:

CheckboxWithLabel.js
import {useState} from 'react';

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

const onChange = () => {
setIsChecked(!isChecked);
};

return (
<label>
<input type="checkbox" checked={isChecked} onChange={onChange} />
{isChecked ? labelOn : labelOff}
</label>
);
}
__tests__/CheckboxWithLabel-test.js
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

// Note: running cleanup afterEach is done automatically for you in @testing-library/[email protected] or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);

it('CheckboxWithLabel changes the text after click', () => {
const {queryByLabelText, getByLabelText} = render(
<CheckboxWithLabel labelOn="On" labelOff="Off" />,
);

expect(queryByLabelText(/off/i)).toBeTruthy();

fireEvent.click(getByLabelText(/off/i));

expect(queryByLabelText(/on/i)).toBeTruthy();
});

The code for this example is available at examples/react-testing-library.

Власні перетворювачі коду

If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using @babel/core:

custom-transformer.js
'use strict';

const {transform} = require('@babel/core');
const jestPreset = require('babel-preset-jest');

module.exports = {
process(src, filename) {
const result = transform(src, {
filename,
presets: [jestPreset],
});

return result || src;
},
};

Don't forget to install the @babel/core and babel-preset-jest packages for this example to work.

To make this work with Jest you need to update your Jest configuration with this: "transform": {"\\.js$": "path/to/custom-transformer.js"}.

If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options:

const babelJest = require('babel-jest');

module.exports = babelJest.createTransformer({
presets: ['my-custom-preset'],
});

See dedicated docs for more details.