Сегодня я собираюсь изучить интерфейсное тестирование. Хотя иногда это воспринимается как рутинная работа, тестирование может быть действительно полезной частью разработки. Это отличный способ уменьшить количество ошибок в вашем коде, более глубоко узнать, что ваш код на самом деле делает, и убедиться, что все работает так, как вы намереваетесь. Кроме того, прохождение спецификации модульного теста всегда заставляет меня махать победным флагом размером с зубочистку!
В качестве введения Enzyme - это тестовая утилита от Airbnb, разработанная специально для React, которая работает вместе с выбранным вами средством запуска тестов или поверх него - вы можете использовать Enzyme с несколькими различными средами тестирования, но я собираюсь использовать это с Mocha (который, как оказалось, был тем, для чего изначально был создан Enzyme) и Chai.
Я создал каркас простого приложения с несколькими компонентами React и небольшим магазином Redux. В этом сообщении в блоге я сосредоточусь на составе тестов, но в более широком плане я намерен расширить функциональность приложения наряду с написанием тестов. Конечно, существуют разные мнения о разработке через тестирование в целом, но я предпочитаю использовать тесты для управления реализацией тех функций, которые мне нужны. Тем не менее, я считаю, что для написания надежных и релевантных тестов при работе в интерфейсе необходимо заранее определить, по крайней мере частично, как каждый компонент будет структурирован и как он должен взаимодействовать с другими компонентами.

В качестве отправной точки этого приложения я хочу, чтобы пользователь мог щелкнуть значок и увидеть хайку на экране. Моя первоначальная файловая структура выглядит следующим образом:
. ├── client │ └── index.jsx │ └── components │ └── Root.jsx │ └── Header.jsx │ └── SingleIcon.jsx │ └── Haikus.jsx │ └── SingleHaiku.jsx │ └── store │ └── index.jsx │ └── actions.jsx │ └── reducer.jsx ├── public │ └── icons │ └── index.html │ └── style.css ├── tests │ └── root.spec.js │ └── singleicon.spec.js │ └── haikus.spec.js │ └── singlehaiku.spec.js ├── app.js └── server.js
Для тестирования компонентов как отдельных единиц Enzyme позволяет нам имитировать поведение компонента в среде DOM, создавая «мелкую» копию компонента. Если мы хотим фактически смонтировать компоненты в DOM, чтобы, например, протестировать, как отдельный компонент взаимодействует с элементами вне его области видимости, Enzyme также предлагает API полного рендеринга, но для сегодняшних целей мы будем использовать неглубокий рендеринг.
Вероятно, самая простая часть головоломки - убедиться, что один компонент правильно отображает другой. В tests / root.spec.js:
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import Root from '../client/components/Root';
describe('<Root />', () => {
const shallowRoot = shallow(Root);
it('should initially render 1 <Header />, 1 <SingleHaiku />, and 8 <SingleIcon /> components', () => {
expect(shallowRoot.find(Header)).to.have.length(1);
expect(shallowRoot.find(SingleHaiku)).to.have.length(1);
expect(shallowRoot.find(SingleIcon)).to.have.length(8);
});
Здесь мы устанавливаем переменную для хранения нашей поверхностной версии корневого компонента, а затем делаем утверждения, используя эту переменную, чтобы убедиться, что она ведет себя так, как мы хотим, чтобы наш корневой компонент. Возвращаемое значение «find» дает нам все узлы, с которыми он сталкивается в нашем дереве shallowRoot, которые соответствуют его аргументу, который мы можем использовать, чтобы определить, успешно ли сам наш корень отображает желаемые компоненты. (На данный момент, хотя наш компонент SingleHaiku изначально будет пустым, я все же хочу, чтобы он отображался на странице. После того, как я достигну некоторых функциональных возможностей базового уровня, я могу перейти к оптимизации.)
В этом же наборе мой следующий тест проверяет, сохраняет ли наш корень состояние, и это состояние инициализируется как объект (сначала пустой). Это будет домом для каждого нового хайку, выбранного при щелчке по значку:
it('has a state containing an object', () => {
const rootState = shallowRoot.state();
expect(rootState).to.be.an('object');
});
Следуя этому, мы хотим убедиться, что событие щелчка наших значков действительно устанавливает новое состояние хайку посредством обработки событий. Здесь мы добавим шпионскую утилиту от Sinon, чтобы наблюдать, как ведут себя наши функции. В tests / singleicon.spec.js:
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import { spy } from 'sinon';
import SingleIcon from '../client/components/SingleIcon';
import Root from '../client/components/Root';
describe('<SingleIcon />', () => {
beforeEach('setHaiku spy', () => {
const mockSetHaiku = spy();
const icon = shallow(<SingleIcon onClick={mockSetHaiku} />)
});
it('calls setHaiku', () => {
icon.simulate('setHaiku', {});
expect(mockSetHaiku.calledOnce).to.be.true;
});
});
Теперь мы смоделировали обработчик событий с помощью нашей переменной mockSetHaiku и убедились, что он вызывается при щелчке по нашему значку, но нам нужно вернуться к нашей корневой спецификации, чтобы проверить, что это событие щелчка вызывает там обновление состояния:
// continuing in tests/root.spec.js
import SingleIcon from '../client/components/SingleIcon';
...
it('updates state with a new haiku when SingleIcon's click event is handled', () => {
const icon = shallowRoot.find(SingleIcon).nodes.map(shallow);
icon.simulate('setHaiku', {line1: 'Clouds full of moonlight', line2: 'A siren faintly echoes', line3: 'Unit tests abound'});
expect(shallowRoot.state().line1).to.be.equal('Clouds full of moonlight');
expect(shallowRoot.state().line2).to.be.equal('A siren faintly echoes');
expect(shallowRoot.state().line3).to.be.equal('Unit tests abound');
});
Описанные выше тесты охватывают основную часть того, что нас беспокоит в отношении наших компонентов: они правильно устанавливаются и что наши события вызывают соответствующие эффекты. Мы можем написать аналогичные тесты, чтобы помочь нам определить, где именно мы хотим, чтобы наш первоначальный список хайку был поставлен, где и как передаются реквизиты, и продолжить добавление функций оттуда.
Если мы задействуем Redux, мы, конечно же, хотим протестировать и это. Мы хотим знать, что наши создатели действий предоставляют нам правильно отформатированные действия и что наш редуктор действует именно так, как мы хотим, и точно обновляет состояние. Нам не нужен Enzyme для этого (достаточно Chai или вашего любимого средства запуска тестов, поскольку нам обычно не нужно имитировать наши прекрасные чистые функции Redux), но это все равно очень важно!
Вы можете запустить каждый набор тестов напрямую с помощью следующей команды в CLI (см. Документацию для информации по установке и обратите внимание на зависимости - Enzyme v3 был недавно представлен и требует установки дополнительного модуля Адаптер) или настройте файл package.json, включив в него тестовый скрипт, указывающий путь к их путям к файлам:
// specify your desired spec file path and run it with mocha $ mocha tests/root.spec.js --compilers js:babel-register
Не забудьте попытаться пройти тесты, чтобы убедиться, что они верны, когда вы их пройдете! Теперь, когда мы написали краткие, легко усваиваемые тесты для наших компонентов, у нас есть не только подробная дорожная карта для достижения желаемой функциональности, но и мы можем убедиться, что каждая функция работает по мере ее создания. Удачного тестирования!