существуетв предыдущем постеМы рассказали, что такое модульное тестирование, и познакомились с основами использования фреймворка модульного тестирования 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
изa
label и убедитесь, что найдено 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 есть три режима рендеринга:
-
Shallow Renderingвести переписку
react-test-renderer/shallow
-
Full DOM Renderingвести переписку
react-dom/test-utils
-
Static Renderingвести переписку
react-test-renderer
Если вы можете понять переднее лицо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получены со склада.