Spec-Zone.ru › Jest

Тестирование приложений React

В Facebook мы используем Jest для тестирования приложений React.

Настройка

Настройка с помощью Create React App

Если вы новичок в React, мы рекомендуем использовать Create React App. Она готова к использованию и поставляется с Jest! Вам потребуется только добавить react-test-renderer для рендеринга снимков.

Запуск

  • npm
  • Yarn
npm install --save-dev react-test-renderer
yarn add --dev react-test-renderer

Настройка без Create React App

Если у вас есть существующее приложение, вам нужно установить несколько пакетов, чтобы все работало вместе. Мы используем пакет babel-jest и пресет Babel react, чтобы преобразовать наш код внутри тестовой среды. Также см. использование Babel.

Запуск

  • npm
  • Yarn
npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

Ваш файл package.json должен выглядеть примерно так (где <current-version> — фактическая последняя версия номера пакета). Пожалуйста, добавьте в него записи scripts и jest configuration:

{
  "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"
  }
}
module.exports = {
  presets: [
    '@babel/preset-env',
    ['@babel/preset-react', {runtime: 'automatic'}],
  ],
};
babel.config.js

И всё готово!

Тестирование снимков

Давайте создадим тест снимка для компонента Link, который рендерит гиперссылки:

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>
  );
}
Link.js

Примечание: Примеры используют функциональные компоненты, но компоненты класса также можно протестировать аналогичным образом. См. React: Функциональные и Классовые компоненты. Важно, что с компонентами класса мы ожидаем, что Jest будет использоваться для тестирования свойств, а не методов напрямую.

Теперь давайте используем тестовый рендерер React и функцию создания снимков Jest, чтобы взаимодействовать с компонентом и захватить рендеренный вывод, создав файл снимка:

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();
});
Link.test.js

При запуске yarn test или jest, это произведёт выходной файл, подобный этому:

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>
`;
__tests__/__snapshots__/Link.test.js.snap

В следующий раз при запуске тестов, рендеренный вывод будет сравнен с ранее созданным снимком. Снимок должен быть сохранён вместе с изменениями кода. Когда тест снимка терпит неудачу, нужно проверить, является ли это ожидаемым или непреднамеренным изменением. Если изменение ожидается, можно вызвать Jest с jest -u, чтобы перезаписать существующий снимок.

Код для этого примера доступен на examples/snapshot.

Тестирование снимков с подменами, Enzyme и React 16+

Существует нюанс при тестировании снимков при использовании Enzyme и React 16+. Если вы подменяете модуль следующим образом:

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

Тогда вы увидите предупреждения в консоли:

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 вызывает эти предупреждения из-за того, как он проверяет типы элементов, а подменённый модуль не проходит эти проверки. У вас есть следующие варианты:

  1. Отобразить как текст. Таким образом, вы не увидите свойства, переданные подставленному компоненту в снимке, но это просто:
    jest.mock('./SomeComponent', () => () => 'SomeComponent');
  2. Отобразить как пользовательский элемент. DOM-«пользовательские элементы» не проверяются на наличие чего-либо и не должны вызывать предупреждения. Они записываются в нижнем регистре и содержат тире в имени.
    jest.mock('./Widget', () => () => <mock-widget />);
  3. Использовать react-test-renderer. Тестовый рендерер не заботится о типах элементов и с готовностью примет, например, SomeComponent. Вы можете проверить снимки с помощью тестового рендерера и отдельно проверить поведение компонента с помощью Enzyme.
  4. Отключить предупреждения (следует сделать в вашем файле настройки Jest):
    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
    Это обычно не должно быть вашим выбором, так как могут быть потеряны полезные предупреждения. Однако в некоторых случаях, например, при тестировании компонентов react-native, мы рендерим react-native теги в DOM и многие предупреждения не имеют отношения. Другой вариант — подменить console.warn и подавить определенные предупреждения.

DOM-тестирование

Если вам нужно проверить и манипулировать рендеренными компонентами, вы можете использовать react-testing-library, Enzyme или TestUtils React. Следующие два примера используют react-testing-library и Enzyme.

react-testing-library

  • npm
  • Yarn
npm install --save-dev @testing-library/react
yarn add --dev @testing-library/react

Давайте реализуем флажок, который переключает метки:

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>
  );
}
CheckboxWithLabel.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/react@9.0.0 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();
});
__tests__/CheckboxWithLabel-test.js

Код этого примера доступен на examples/react-testing-library.

Enzyme

  • npm
  • Yarn
npm install --save-dev enzyme
yarn add --dev enzyme

Если вы используете версию React ниже 15.5.0, вам также потребуется установить react-addons-test-utils.

Давайте перепишем тест выше, используя Enzyme вместо react-testing-library. В этом примере мы используем поверхностный рендерер Enzyme.

import Enzyme, {shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import CheckboxWithLabel from '../CheckboxWithLabel';

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

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

  expect(checkbox.text()).toEqual('Off');

  checkbox.find('input').simulate('change');

  expect(checkbox.text()).toEqual('On');
});
__tests__/CheckboxWithLabel-test.js

Код этого примера доступен на examples/enzyme.

Пользовательские преобразователи

Если вам нужна более сложная функциональность, вы также можете создать свой собственный преобразователь. Вместо использования babel-jest, вот пример использования @babel/core.

'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;
  },
};
custom-transformer.js

Не забудьте установить пакеты @babel/core и babel-preset-jest для работы этого примера.

Для работы с Jest вам нужно обновить вашу конфигурацию Jest следующим образом: "transform": {"\\.js$": "path/to/custom-transformer.js"}.

Если вы хотите создать преобразователь с поддержкой Babel, вы также можете использовать babel-jest для создания составного и передачи ваших пользовательских параметров конфигурации:

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

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

См. посвящённую документацию для получения более подробной информации.

© 2022 Facebook, Inc.
Licensed under the MIT License.
https://jestjs.io/docs/tutorial-react

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API