Используйте JEST для написания модульных тестов для компонентов React.

модульный тест React.js
Используйте JEST для написания модульных тестов для компонентов React.

Изображение через https://blog.algolia.com

существуетв предыдущем постеМы рассказали, что такое модульное тестирование, и познакомились с основами использования фреймворка модульного тестирования Jest. В этой статье мы подробно поговорим о том, как писать модульные тесты для компонентов React с помощью Jest.

  • Результатом рендеринга компонента React является дерево компонентов, и все дерево в конечном итоге будет преобразовано в древовидную структуру, состоящую исключительно из элементов HTML.
  • Актуальные компоненты могут иметь состояние, а изменения в состоянии повлияют на результат рендеринга
  • Компоненты React могут иметь функции жизненного цикла, которые выполняются в определенные моменты времени.

Зная, что тестировать, следующий вопрос — как выполнить компонент React и написать утверждения. Как выполнить компонент React? Увидев этот вопрос, считается, что большинство людей сбиты с толку. обычно не напрямуюReactDOM.render? хорошо,ReactDOM.renderМожно выполнить компонент React и отобразить его на странице, но это не способствует написанию тестового кода.

Есть ли более простой способ? На самом деле React уже предоставил нам инструменты, давайте посмотрим.

Инструменты тестирования, предоставляемые React

Упоминается двумя библиотеками для тестирования реагирования реагировать компонент в официальных документах. Давай представим.

react-test-renderer

говорящийreact-test-rendererПеред этим поговорим о том, чтоrenderer. React впервые использовался для разработки веб-страниц, поэтому ранняя библиотека React также содержала много логики, связанной с DOM. Позже дизайнерские идеи React были постепенно перенесены в другие сценарии, наиболее известным из которых былReact Native. Для гибкости и расширяемости код React разбит на React основной код и различныеrenderer. React поставляется с 3 рендерерами, первые два являются общими:

  • react-domОтвечает за отрисовку компонентов на страницах браузера.

  • react-native-rendererОтвечает за рендеринг компонентов в различные «представления» на собственной сцене.

и сегодняшнийreact-test-rendererОн отвечает за вывод компонента в виде объекта JSON, который мы можем пройти, подтвердить или выполнить тестирование моментальных снимков.

Примечание:здесь

react-dom/test-utils

В первую очередь видно из названия, что эта библиотека включена вreact-domсередина.所以它只是react-domКонечно, эта среда DOM может бытьjsdomэта смоделированная среда. (среда выполнения Jest по умолчанию — jsdom)

как выбрать?

Читая это, вы можете спросить,react-test-rendererа такжеreact-dom/test-utilsОба до сих пор похожи. Когда выбирать какую библиотеку? Согласно реальному опыту использования автора, проще говоря:

  • Если вам нужно протестировать события (например, щелчок, изменение, размытие и т. д.), используйтеreact-dom/test-utils
  • В других случаях используйте более простые и гибкиеreact-test-renderer

Как использовать реактивный тестовый рендерер

На практике есть два варианта использования react-test-renderer:

  • мелкий рендеринг: компонент будет визуализирован только в один слой (компоненты React в дочерних элементах не будут визуализированы)
  • полный рендеринг: компонент будет полностью визуализирован

Теперь давайте конкретно рассмотрим разницу между двумя методами на примере.

Предположим, у нас есть следующие два компонента:

const Link = ({to, children}) => (
  <a className="my-link" href={to} target="_blank" rel="noopener noreferrer">{children}</a>
);

const Header = () => (
  <div>
    <span className="brand">Hello world</span>
    <Link to="https://jd.com">JD</Link>
    <Link to="http://butler.jd.com">Butler</Link>
    <Link to="http://lrc.jd.com">lrc</Link>
  </div>
);

shallow render

Вспомогательные классы, связанные с поверхностным рендерингом, существуют вreact-test-renderer/shallowПод пространством мы сначала вводим и создаем экземпляр:

import ShallowRenderer from 'react-test-renderer/shallow';

const renderer = new ShallowRenderer();

Экземпляр ShallowRenderer предоставляет нам два метода:

  • render()Используется для визуализации компонента. Вы можете думать об экземпляре ShallowRenderer как о «пространстве», в котором находятся визуализируемые компоненты.
  • getRenderOutput()

describe('Header', () => {
  it('should render a top level div', () => {
    const renderer = new ShallowRenderer();
    renderer.render(<Header />);
    const result = renderer.getRenderOutput();
    expect(result.type).toBe('div');
  });

  it('should render 3 Link', () => {
    const renderer = new ShallowRenderer();
    renderer.render(<Header />);
    const result = renderer.getRenderOutput();
    const childrenLink = result.props.children.filter(c => c.type === Link);
    expect(childrenLink.length).toBe(3);
  });
});

Мы впервые проверилиHeaderЭлемент заднего верхнего слоя рендеринга является однимdiv. Затем во втором варианте использования проверяется, что результат рендеринга содержит 3LinkЭкземпляр компонента. Поскольку поверхностный рендеринг отображает только один слой, информация, которую можно проверить, относительно проста. Лучше проверить, что структура вывода компонента соответствует ожидаемой.

full render

Далее посмотрите на полный рендер.

Впервые введены в библиотеку инструментов:

import TestRenderer from 'react-test-renderer';

передачаTestRendererизcreateметод и передать компонент для рендеринга, чтобы получитьTestRendererпример. В этом экземпляре существуют следующие методы и свойства:

  • .toJSON(): создает объект JSON, представляющий результат рендеринга. Этот объект содержит только такие объекты, как<div>(веб-платформа) или<View(Родная платформа) такой родной узел. Он не содержит информационных пользовательских компонентов. Подходит дляsnapshot testing.
  • .toTree():а также.toJSON()Аналогично, но более информативно, включая информацию о компонентах, разработанных пользователями.
  • .update(element): обновить дерево компонентов из последней визуализации, передав новый элемент.
  • .umount()
  • .getInstance(): возвращает экземпляр компонента React, соответствующий корневому узлу, если он существует. Невозможно получить, является ли компонент верхнего уровня функциональным компонентом.
  • .root: это свойство хранит тестовый экземпляр, соответствующий корневому узлу. Этот пример предоставляет нам ряд методов для написания утверждений.

Теперь давайте посмотрим, какие методы и свойства доступны для использования в тестовом экземпляре (полный список, пожалуйста,обратитесь сюда:

  • .find()а также.findAll(): используется для поиска тестовых экземпляров, соответствующих определенным критериям. Разница в том, что.find()Строго требуется, чтобы был только 1 тестовый экземпляр, удовлетворяющий условию вида дерева узлов, и если нет или более 1 тестового экземпляра, будет выдано исключение. Это различие также относится к следующим двум наборам методов.
  • .findByType()а также.findAllByType: Используется для поиска тестовых экземпляров определенного типа. Тип здесь может бытьdivЭтот родной тип также может бытьLinkЭтот написанный пользователем компонент React.
  • .findByProps()а также.findAllByProps(): Используется для поиска тестовых экземпляров, реквизиты которых соответствуют определенной структуре.
  • .instance: экземпляр компонента React, соответствующий этому тестовому экземпляру.

Теперь давайте посмотрим на полный тестовый пример:

describe('Header', () => {
  it('should render 3 a tag with className "my-link"', () => {
    const testRenderer = TestRenderer.create(<Header />);
    const testInstance = testRenderer.root;
    expect(testInstance.findAll(node => node.type === 'a' && node.props.className === 'my-link')).toHaveLength(3);
  });
});

В этом случае мы используем.find()метод найденclassNameдляmy-linkизalabel и убедитесь, что найдено 3.

Как использовать react-dom/test-utils

Теперь давайте посмотрим, как писать модульные тесты для компонентов, предполагающих взаимодействие с пользователем. Прежде всего, просто понятьreact-dom/test-utilsбазовое использование.

Сначала представим классы инструментов:

import  ReactTestUtils  from  'react-dom/test-utils';

ReactTestUtilsОбычно мы используем следующие методы для объектов (полный список методов см.здесь):

  • .Simulate.{evnentName}(): Имитирует запуск характерного события на заданном узле DOM.Simulateможет вызватьReact для поддержки всех типов событий.
  • renderIntoDocument(): визуализирует компонент React в отдельный DOM. Примечание. Этот метод зависит от среды DOM. Но не волнуйтесь, Jest интегрирован по умолчанию.jsdom. Этот метод возвращает экземпляр компонента React, который был обработан.
  • scryRenderedDOMComponentsWithClass()а такжеfindRenderedDOMComponentWithClass():查找匹配特定类名的 DOM 元素。 Разница в том, чтоscryRenderedDOMComponentsWithClass()会查找所有元素。 а такжеfindRenderedDOMComponentWithClass()будет предполагать, что страница имеет и только 1 элемент, соответствующий условию, в противном случае выдается исключение.
  • scryRenderedDOMComponentsWithTag()а такжеfindRenderedDOMComponentWithTag(): найти соответствие определенному типу тегов элементов DOM.

Давайте ознакомимся с реальным использованием через конкретный компонент.

Предположим, у нас есть следующееButtonКомпоненты:

import React from 'react';

class Button extends React.Component {
  constructor() {
    super();

    this.state = { disabled: false };
    this.handClick = this.handClick.bind(this);
  }

  handClick() {
    if (this.state.disabled) { return }
    if (this.props.onClick) { this.props.onClick() }
    this.setState({ disabled: true });
    setTimeout(() => {this.setState({ disabled: false })}, 200);
  }

  render() {
    return (
      <button className="my-button" onClick={this.handClick}>{this.props.children}</button>
    );
  }
};

export default Button;

Его основная функция — нажатьbuttonВыполняется, когда элементonClick

onClickОбратный вызов этой логики:

  it('should call onClick callback if provided', () => {
    const onClickMock = jest.fn();
    const testInstance = ReactTestUtils.renderIntoDocument(
      <Button onClick={onClickMock}>hello</Button>
    );
    const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
    ReactTestUtils.Simulate.click(buttonDom);
    expect(onClickMock).toHaveBeenCalled();
  });

onClickMockИ пропустите его, чтобы функционировать как обратноButtonкомпоненты. Затем используйтеReactTestUtils.Simulate.clickИмитирует запуск события клика. Наконец подтвердитеonClickMockназывается.

Примечание. Для фиктивного метода использованияв предыдущем постеСуществует подробное введение, добро пожаловать на чтение.

Далее давайте проверим переход в отключенное состояние в течение 200 миллисекунд после клика:

  it('should be throttled to 200ms', () => {
    const testInstance = ReactTestUtils.renderIntoDocument(<Button>hello</Button>);
    const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
    ReactTestUtils.Simulate.click(buttonDom);
    expect(testInstance.state.disabled).toBeTruthy();
    jest.advanceTimersByTime(199);
    expect(testInstance.state.disabled).toBeTruthy();
    jest.advanceTimersByTime(1);
    expect(testInstance.state.disabled).toBeFalsy();
  });

Из-за задействованной логики таймера мы используем Jest, предоставленный в этом варианте использования.функция имитации таймера. Whest подробное использование, пожалуйста, обратитесь к официальной документации.

Enzyme

Две библиотеки инструментов тестирования, поставляемые с React, были представлены ранее. Далее краткое введение в библиотеку инструментов тестирования React с открытым исходным кодом Airbnb.Enzyme.

Нижний слой Enzyme на самом деле основан наreact-test-rendererа такжеreact-dom/test-utilsиз. Однако он инкапсулирован на основе этих двух, чтобы обеспечить более простые и легкие в использовании методы запросов и утверждений. Концептуально Enzyme также очень похож на оба. В Enzyme есть три режима рендеринга:

Если вы можете понять переднее лицоreact-test-rendererа такжеreact-dom/test-utilsвведения, то начать работу с Enzyme должно быть очень легко. Использование Enzyme здесь подробно описываться не будет.

Давайте используем Enzyme, чтобы переписать предыдущийButtonКомпонентный письменный тест:

describe('Button', () => {
  it('should be throttled to 200ms', () => {
    const wrapper = mount(<Button>hello</Button>);
    wrapper.find('.my-button').simulate('click');
    expect(wrapper.state('disabled')).toBeTruthy();
    jest.advanceTimersByTime(199);
    expect(wrapper.state('disabled')).toBeTruthy();
    jest.advanceTimersByTime(1);
    expect(wrapper.state('disabled')).toBeFalsy();
  });

  it('should call onClick callback if provided', () => {
    const onClickMock = jest.fn();
    const wrapper = mount(<Button onClick={onClickMock}>hello</Button>);
    wrapper.find('.my-button').simulate('click');
    expect(onClickMock).toHaveBeenCalled();
  });
});

Теперь мы можем предоставить через Enzyme.find()метод поиска узлов DOM через.state()

напиши в конце

Эта статья кратко знакомитreact-test-rendererа такжеreact-dom/test-utilsДва брата и Энзим. Вы можете выбрать, какой инструмент выбрать в проекте в соответствии с вашими предпочтениями.

Весь код в этой статье можно найти вloveky/unit-testing-react-componentполучены со склада.

Связь