Что и как тестировать с помощью Jest и Enzyme. Полная инструкция по тестированию React-компонентов
Перевод статьи Alona Pysarenko: What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing.
Что и как тестировать с помощью Jest и Enzyme. Полная инструкция по тестированию React-компонентов
Перевод статьи Alona Pysarenko: What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing.

Тестирование React-компонентов может оказаться сложной задачей как для начинающих, так и для опытных разработчиков, которые уже работали с тестами. Вам может быть интересно сравнить ваши собственные подходы с теми, которые мы используем в нашем проекте. Для покрытия тестами кодовой базы вам необходимо знать, какие компоненты должны быть протестированы и какой код точно должен быть покрыт тестами.
Во время чтения я расскажу о следующих темах:
- Определение правильного порядка тестирования компонентов на основе структуры проекта.
- Нахождение того, что можно опустить в покрытии тестами (что можно не тестировать).
- Выявление необходимости использовать тестирование на основе снимков.
- Определение, что тестировать в компоненте и в каком порядке.
- Предоставление подробных примеров кода.
Статья требует, чтобы читатель уже знал о Jest и Enzyme. Информацию об установке и настройке можно легко найти в интернете или на официальных сайтах.
Представим следующую ситуацию: вам нужно покрыть кодовую базу проекта тестами, и так с чего вам начать и что вы должны получить в конце тестирования? 100% покрытие тестами? Это показатель, к которому вы должны стремиться, но в большинстве случаев вы его не получите. Почему? Потому что вы не должны тестировать весь код. Мы выясним, почему и что следует оставить без тестов. Более того, 100% покрытие не всегда гарантирует, что компонент будет полностью протестирован. Также, нет никакой гарантии, что он сообщит вам если что-то было изменено. Не стремитесь к процентам, избегайте написания фальшивых тестов и старайтесь не потерять основные детали компонента.
Определение правильного порядка тестирования компонентов на основе структуры проекта
Давайте обсудим этот вопрос на следующей части структуры проекта:

Я взяла каталог shared, потому что он очень важен. Он состоит из компонентов, которые используются на нескольких разных страницах проекта. Они переиспользуемые и обычно маленькие и несложные. Но если тот или иной компонент выйдет из строя, это приведёт к сбою в других местах. Поэтому мы должны быть уверены, что они написаны правильно. Структура этого каталога разделена на несколько папок, каждая из которых содержит компоненты.

Как определить правильный порядок тестирования компонентов в каталоге shared:
- Всегда следуйте правилу от простого к сложному. Проанализируйте каждый каталог и определите, какие компоненты являются независимыми, т.е. их отрисовка не зависит от других компонентов, они самодостаточны и могут использоваться отдельно как единое целое. Из структуры выше — это каталог
inputsв папкеforms. Он содержит компоненты ввода вredux-forms, такие, какTextInput,SelectInput,CheckboxInput,DateInputи др. - Затем нужно определить вспомогательные компоненты, которые часто используются в компонентах
inputs, но их следует проверять отдельно. Это каталогutils. Компоненты в этой папке не сложные, но очень важные. Они часто переиспользуются и помогают с повторяющимися действиями. - Следующий шаг — определить, какие компоненты можно использовать независимо друг от друга. Если такие есть, возьмите их для тестирования. В нашей структуре — это
widgets, небольшие компоненты с простой функциональностью. Они будут третьим пунктом в очереди на тестирование. - Далее проанализируйте остальные каталоги и определите более сложные компоненты, которые можно использовать независимо или в сочетании с другими компонентами. В нашем случае — это каталог
modals, эти компоненты будут подробнее описаны ниже. - Самые сложные для тестирования компоненты останутся в конце. Это каталог
hocиfieldsиз папкиforms. Как вам определить, какой из них следует тестировать первым? Я беру каталог, компоненты которого уже встречались в тестируемых компонентах. Например, компоненты изhocкаталога присутствовали в компонентеwidgets, поэтому я уже знаю, где и с какой целью используется этот каталог и его компоненты. - Последняя папка
fieldsсодержит компоненты, связанные сredux-forms.
Окончательный порядок компонентов (на основе нашего примера) будет выглядеть следующим образом:

Следуя этому порядку, вы постепенно увеличиваете сложность тестируемых компонентов, таким образом, когда дело доходит до работы с более сложными компонентами, вы уже знаете, как ведут себя самые маленькие. Не берите для тестирования, например, поле «массив» если вы не знаете, как проверить поле «текст»; не берите компонент с использованием redux-forms, если вы не тестировали само поле «форма». Будьте последовательны в своем выборе, не берите первый компонент, который приходит в голову и включите логику. Конечно, структура вашего проекта может отличаться; он может иметь другие имена каталогов или содержать дополнительные компоненты, действия или редьюсеры, но логика определения порядка тестирования компонентов одинаковая.
Давайте определим, что следует пропустить при тестировании:
- Сторонние библиотеки. Не тестируйте функции, взятые из другой библиотеки; вы не несете ответственности за этот код. Пропустите его или имитируйте выполнение, если это нужно для тестирования вашего кода.
- Константы. Название говорит само за себя. Они неизменяемые; это набор статического кода, который не предназначен для изменения.
- Встроенные стили (если они используются в компоненте). Чтобы проверить встроенные стили, необходимо дублировать объект со стилями в тесте; если изменяется объект стилей, необходимо также изменить его в тесте. Не дублируйте код компонента в тестах, вы можете забыть изменить его в тестах. Более того, ваш коллега никогда не догадается о дублировании. В большинстве случаев встроенные стили не изменяют поведение компонента, следовательно, они не нуждаются в проверке. Возможно исключение, если ваши стили изменяются динамически.
- Всё, что не связано с тестируемым компонентом. Пропустите покрытие тестами компоненты, которые были импортированы, будьте осторожны, если один компонент обернут в другой. В таком случае не тестируйте оболочку, анализируйте и тестируйте все отдельно.
Итак, как вы на самом деле пишете тесты? Я объединяю два подхода к тестированию:
- Тестирование при помощи снимков (Snapshot-тестирование).
- Тестирование логики компонентов.
Тестирование при помощи снимков — полезный инструмент тестирования, если вы хотите быть уверенным, что пользовательский интерфейс не изменился. Когда вы впервые сталкиваетесь с этим инструментом тестирования, возникают вопросы, связанные с организацией и управлением снимками. Принцип работы очень прост, но, к сожалению, он нигде подробно не описан; на официальном сайте jestjs.io очень плохо описана работа с тестирование при помощи снимков.
Как тестировать при помощи снимков
Шаг 1. Напишем тест для компонента, и в функции expect используем метод .toMatchSnapshot(), который создаст снимок.
it("render correctly text component", () => {
const TextInputComponent = renderer.create(<TextInput />).toJSON();
expect(TextInputComponent).toMatchSnapshot();
});
Шаг 2. При первом запуске теста на одном уровне с тестом будет создан каталог с именем __snapshots__ с автоматически созданным файлом с расширением .snap внутри.
Снимок выглядит так:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render TextInput correctly component 1`] = `
<input
className="input-custom"
disabled={undefined}
id={undefined}
name={undefined}
onBlur={undefined}
onChange={[Function]}
pattern={undefined}
placeholder={undefined}
readOnly={false}
required={undefined}
type="text"
value={undefined}
/>
`;
Шаг 3. Отправим снимок в репозиторий и сохраним его вместе с тестом.
Если компонент был изменен, то вам нужно обновить снимок с флагом --updateSnapshot или использовать короткий флаг u.
Снимок создан; как это работает?
Рассмотрим две ситуации:
1. Компонент изменился
- Запускаем тесты.
- Создается новый снимок, который сравнивается с ранее созданным снимком, хранящимся в каталоге
__snapshots__. - Не удалось выполнить тест, так как снимок отличается.

2. Компонент не изменился
- Запускаем тесты.
- Создается новый снимок, который сравнивается с ранее созданным снимком, хранящимся в каталоге
__snapshots__. - Тест пройден, так как снимки идентичны.

Все нормально, когда тестируются небольшие компоненты без логики, просто отрисовка UI, но, как показывает практика, на реальных проектах таких компонентов нет. А если они есть, то совсем в небольшом количестве.
Достаточно ли снимков для полного тестирования компонентов?
Основные инструкции по тестирования компонентов
-
Один компонент должен иметь один снимок. Если один снимок завершается неудачей, скорее всего, остальные тоже потерпят неудачу, поэтому не создавайте и не храните кучу ненужных снимков, забивающих пространство и запутывающих разработчиков, которые будут читать ваши тесты после вас. Конечно, бывают исключения, когда необходимо проверить поведение компонента в двух состояниях, например, в компонент в состоянии перед открытием всплывающего окна и после открытия. Однако даже такой вариант можно заменить на такой: первый тест сохраняет снимок с состоянием компонента по умолчанию, без всплывающего окна, а второй тест имитирует событие и проверяет наличие определенного класса. Таким образом, вы можете легко обойти создание нескольких снимков.
-
Тестирование свойств. Как правило, тестирование свойств я разделяю на два шага. Во-первых, проверьте значение свойств по умолчанию; когда компонент отрисовывается, я ожидаю, что значение будет равно значению по умолчанию из
defaultPropsесли оно там определенно. Во-вторых, я проверяю пользовательские свойства; я устанавливаю свое собственное значение и ожидаю, что оно будет получено после отрисовки компонента. -
Тестирование типов данных. Чтобы проверить, какой тип данных приходит в свойства или какие данные получены после определенных действий, я использую специальную библиотеку
jest-extended(Дополнительный вычислитель для Jest), она имеет расширенный набор вычислителей, которые отсутствуют вJest. С помощью этой библиотеки тестировать типы данных намного проще и приятнее. Тестирование типов данных — спорный вопрос. Некоторые разработчики могут возразить против тестирования типов данных, потому что это сторонний пакет и он не должен тестироваться, но я настаиваю на тестировании типов данных для компонентов, потому что я не тестирую саму функциональность пакета, я просто гарантирую, что типы данных верны. Тип данных является очень важной частью программирования и не должен игнорироваться. -
Тестирование событий. После создания снимков и покрытия свойств тестами можно быть уверенным в корректной отрисовке компонента, но этого недостаточно для полного покрытия при наличии событий в компоненте. Вы можете проверить события несколькими способами; наиболее широко используемые:
- Создаете фиктивное событие => имитируете событие => ожидаете вызов события.
- Создаете фиктивное событие => имитируете событие с передачей необходимых параметров => отрисовываете компонент => ожидаете вызов события с передаваемыми параметрами.
- Передаете необходимые свойства => отрисовываете компонент => имитируете событие => ожидаете вызов события с передаваемыми параметрами.
-
Тестирование условий. Очень часто у вас могут быть условия для вывода определенного класса, отрисовки определенного раздела кода, передачи необходимых свойств и т.д. Не забывайте об этом, потому что со значениями по умолчанию будет пройдена только одна ветка, а вторая останется непроверенной. Так в сложных компонентах с вычислениями и множественными условиями можно пропустить несколько веток. Чтобы убедиться, что все части кода покрыты тестами используйте инструменты покрытия и визуально проверяйте, какие ветки покрыты тестами, а какие нет.
-
Тестирование состояний. Для проверки состояния в большинстве случаев необходимо написать два теста:
- Первый проверяет текущее состояние.
- Второй проверяет состояние после вызова события. Отрисовываете компонент => вызываете функцию непосредственно в тесте => проверяете, как изменилось состояние. Чтобы вызывать функцию компонента, вам необходимо получить экземпляр компонента и только после этого вызывать его методы (пример показан в следующем тесте).
После того, как вы пройдете через этот список инструкций, ваш компонент будет покрыт тестами от 90% до 100%. Я оставила 10% для особых случаев, которые не были описаны в статье, но могут встретиться в коде.
Пример тестирования
Давайте перейдем к примерам и шаг за шагом покроем компоненты тестами, по описанной выше схеме.
1. Тестирование компонента из директории forms/inputs
Возьмем один компонент из директории forms/inputs; пусть это будет компонент DateInput.js для выбора даты.

Компонент DateInput использует библиотеку react-datepicker с двумя утилитами: valueToDate (преобразует значение в дату) и dateToValue - это метод для манипулирования датой и PropTypes для проверки свойств React.
По коду компонента мы видим список свойств по умолчанию, с помощью которых компонент должен быть отрисован:
const defaultProps = {
inputClassName: "input-custom",
monthsShown: 1,
dateFormat: "DD.MM.YYYY",
showMonthYearsDropdowns: false,
minDate: moment()
};
Все свойства подходят для создания снимков, за исключением одного minDate: moment(), moment() — будет давать нам текущую дату каждый раз, когда мы запускаем тест, и тест на основе снимков будет терпеть неудачу, потому что будет хранить устаревшую дату. Решение состоит в том, чтобы создать фиктивное значение:
const defaultProps = {
minDate: moment(0)
};
minDate — это свойство, которое мне нужно в каждом визуальном компоненте; чтобы избежать дублирования, я создаю HOC, который получит defaultProps и вернет компонент:
import TestDateInput from "../DateInput";
const DateInput = props => <TestDateInput {...defaultProps} {...props} />;
Не забывайте о moment-timezone, особенно если ваши тесты будут выполняться разработчиками из других стран в другом часовом поясе. Они будут получать фиктивные значения, но со сдвигом часового пояса. Решение — установить часовой пояс по умолчанию:
const moment = require
.requireActual("moment-timezone")
.tz.setDefault("America/Los_Angeles");
Теперь компонент ввода даты готов к тестированию:
- Создание снимка:
it("render correctly date component", () => {
const DateInputComponent = renderer.create(<DateInput />).toJSON();
expect(DateInputComponent).toMatchSnapshot();
});
- Тестирование свойств:
Посмотрите свойства и найдите самый важные; первое свойство для тестирования — showMonthYearsDropdowns, если оно установлено в true, отображается раскрывающийся список для полей месяц и год:
it("check month and years dropdowns displayed", () => {
const props = {
showMonthYearsDropdowns: true
},
DateInputComponent = mount(<DateInput {...props} />).find(
".datepicker"
);
expect(DateInputComponent.hasClass("react-datepicker-hide-month")).toEqual(
true
);
});
Проверим свойство value на null; эта проверка необходима для обеспечения отображения компонента без определённого значения:
it("render date input correctly with null value", () => {
const props = {
value: null
},
DateInputComponent = mount(<DateInput {...props} />);
expect(DateInputComponent.prop("value")).toEqual(null);
});
- Тестирование значения
PropTypes, дата должна быть строкой:
it("check the type of value", () => {
const props = {
value: "10.03.2018"
},
DateInputComponent = mount(<DateInput {...props} />);
expect(DateInputComponent.prop("value")).toBeString();
});
- Тестирование событий:
4.1 Проверим событие onChange, для этого создадим фиктивное событие onChange => отрисовываем компонент ввода даты => имитируем событие изменения даты с новым значением => и, наконец, проверим что событие onChange было вызвано с новым значением.
it("check the onChange callback", () => {
const onChange = jest.fn(),
props = {
value: "20.01.2018",
onChange
},
DateInputComponent = mount(<DateInput {...props} />).find("input");
DateInputComponent.simulate("change", {
target: { value: moment("2018-01-22") }
});
expect(onChange).toHaveBeenCalledWith("22.01.2018");
});
4.2 Убедимся, что всплывающее окно открывается после нажатия на поле с вводом даты, для этого найдем поле с вводом даты => имитируем событие клик => и проверим на наличие всплывающего окна с классом .react-datepicker.
it("check DatePicker popup open", () => {
const DateComponent = mount(<DateInput />),
dateInput = DateComponent.find("input[type='text']");
dateInput.simulate("click");
expect(DateComponent.find(".react-datepicker")).toHaveLength(1);
});
Полный список тестов: DateInput.test.js
2. Тестирование утилит
Код тестируемой утилиты: valueToDate.js
Цель этой утилиты является преобразование значения даты в пользовательском формате. Прежде всего, давайте проанализируем данную утилиту и определим основные кейсы для тестирования:
- Согласно назначению этой утилиты, она преобразует значение, поэтому нам нужно проверить это значение:
- В случае, если значение не определено: мы должны быть уверены, что утилита не вернет исключение (ошибку).
- В случае, если значение переопределено: нам нужно проверить, что утилита возвращает дату.
-
Возвращаемое значение должно принадлежать классу
moment, поэтому он должен быть экземпляромmoment. -
Второй аргумент —
dateFormat; установим его как константу перед тестами. Поэтому он будет передаваться в каждом тесте и возвращать значение в соответствии с форматом даты. Должны ли мы тестироватьdateFormatотдельно? Полагаю, нет. Этот аргумент необязателен; если мы не зададимdateFormat, утилита не сломается, а просто вернет дату в формате по умолчанию; это работаmoment, а мы не должны тестировать сторонние библиотеки. Как я уже упоминала, не стоит забывать о часовом поясе, это очень важный момент, особенно для разработчиков из разных часовых поясов.
Давайте напишем код:
- Напишем тест для первого случая; когда у нас нет значения, оно пустое.
const format = "DD.MM.YYYY";
it("render valueToDate utility with empty value", () => {
const value = valueToDate("", format);
expect(value).toEqual(null);
});
- Проверим, определено ли значение
const date = '21.11.2015',
format = ‘DD.MM.YYYY’;
it("render valueToDate utility with defined value", () => {
const value = valueToDate(date, format);
expect(value).toEqual(moment(date, format));
});
- Проверим, принадлежит ли значение к классу
moment
const date = "21.11.2015",
format = "DD.MM.YYYY";
it("check value is instanceof moment", () => {
const value = valueToDate(date, format);
expect(value instanceof moment).toBeTruthy();
});
Полный список тестов: valueToDate.test.js
3. Тестирование виджетов
Для тестирования виджетов я взяла компонент spinner.
Код виджета: Spinner.js
Выглядит так:

Spinner не нуждается в объяснении, так как почти все веб-ресурсы используют этот компонент.
Так что идем писать тесты:
- Первый шаг — создание снимка
it("render correctly Spinner component", () => {
const SpinnerComponent = mount(<Spinner />);
expect(SpinnerComponent).toMatchSnapshot();
});
- Тестирование свойст
2.1 Проверим свойство title по умолчанию на правильность отображения
it("check prop title by default", () => {
const SpinnerComponent = mount(<Spinner />);
expect(SpinnerComponent.find("p").text()).toEqual("Please wait");
});
2.2 Проверим, что правильно возвращается пользовательское значение для свойства title. Взгляните на код, titel завернут в rawMarkup, и выводится с помощью свойства dangerouslySetInnerHTML.
Код утилиты rawMarkup:
export default function rawMarkup(template) {
return { __html: template };
}
Нужно ли включать тесты rawMarkup в компонент spinner? Нет, это отдельная утилита и её нужно тестировать отдельно от spinner. Нам все равно, как это работает, нам просто нужно знать, что свойство title возвращает правильный результат.
Пояснение: причина использования свойства dangerouslySetInnerHTML в следующем: наш сайт многоязычный, за его перевод отвечает маркетинговая команда. Они могут перевести его просто сочетанием слов или дополнить HTML-тегами, такими как <strong>, <i>, <s> или даже разделить текст на список, используя теги <ol> или <ul>, но мы не знаем точно, как они переведут и что будут использовать в тексте. Нам просто нужно правильно всё это отобразить.
Я объединила два теста в один:
- Возврат правильного пользовательского свойства.
- Правильное отображение заголовка с HTML-тегами.
it("check prop title with html tags", () => {
const props = {
title: "<b>Please wait</b>"
},
SpinnerComponent = mount(<Spinner {...props} />);
expect(SpinnerComponent.find("p").text()).toEqual("Please wait");
});
Возьмем следующее свойство subTitle, оно необязательное и именно поэтому у него нет значения по умолчанию, поэтому пропустим шаг с тестированием значения по умолчанию и проверим пользовательское свойство:
- Убедимся, что текст в
subTitleотрисовывается корректно:
const props = {
subTitle: "left 1 minute"
},
SpinnerComponent = mount(<Spinner {...props} />);
it("render correct text", () => {
expect(
SpinnerComponent.find("p")
.at(1)
.text()
).toEqual(props.subTitle);
});
Мы знаем, что свойство subTitle необязательное, поэтому нам нужно проверить, не отображается ли оно с значением по умолчанию. Просто проверим количество тегов <p>:
it("check subTitle is not rendered", () => {
const SpinnerComponent = mount(<Spinner />);
expect(SpinnerComponent.find("p").length).toEqual(1);
});
- Тестирование типов свойств
- Для свойства
titleожидаемый тип — строка.
it("check prop type for title is string", () => {
const props = {
title: "Wait"
},
SpinnerComponent = mount(<Spinner {...props} />);
expect(SpinnerComponent.find("p").text()).toBeString();
});
- Для свойства
subTitleтакже ожидаемый тип — строка.
const props = {
subTitle: "left 1 minute"
},
SpinnerComponent = mount(<Spinner {...props} />);
it("type for subTitle is string", () => {
expect(
SpinnerComponent.find("p")
.at(1)
.text()
).toBeString();
});
Полный список тестов: Spinner.test.js
4. Тестирование модальных окон (ModalWrapper.js и ModalTrigger.js)
Выглядит так:

Как тестировать модальные окна
Прежде всего, я хочу объяснить, как организованы модальные окна на нашем проекте. У нас есть два компонента: ModalWrapper.js и ModalTrigger.js.
ModalWrapper.js отвечает за макет модального окна. Он содержит контейнер, кнопку закрытия, заголовок и тело.
ModalTrigger.js отвечает за управление модальным окном. Он включает в себя шаблон ModalWrapper.js и содержит события для управления модальным окном (действия открытия, закрытия).
- Код для тестируемого компонента: ModalWrapper.js
Давайте напишем код:
1.1 ModalWrapper.js получает компоненты и отображает их внутри себя. Прежде всего, убедимся, что ModalWrapper.js не сломается без компонента. Создадим снимок со значением по умолчанию:
it("without component", () => {
const ModalWrapperComponent = shallow(<ModalWrapper />);
expect(ModalWrapperComponent).toMatchSnapshot();
});
1.2 Следующим шагом будет имитирование его фактического состояния через отрисовку компонента с переданными ему свойствами:
it("with component", () => {
const props = {
component: () => {}
},
ModalWrapperComponent = shallow(<ModalWrapper {...props} />);
expect(ModalWrapperComponent).toMatchSnapshot();
});
1.3 Проверяем свойства:
- Получаем свойство
modalClassName.
it("render correct class name", () => {
const props = {
modalClassName: "custom-class-name"
},
ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find(
"Modal"
);
expect(ModalWrapperComponent.hasClass("custom-class-name")).toEqual(true);
});
- Получаем свойство
title.
it("render correct title", () => {
const props = {
title: "Modal Title"
},
ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find(
"ModalTitle"
);
expect(ModalWrapperComponent.props().children).toEqual("Modal Title");
});
- Получаем свойство
show.
it("check prop value", () => {
const props = {
show: true
},
ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find(
"Modal"
);
expect(ModalWrapperComponent.props().show).toEqual(true);
});
1.4 Тестирование типов свойств:
- Для свойства
show.
it("check prop type", () => {
const props = {
show: true
},
ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find(
"Modal"
);
expect(ModalWrapperComponent.props().show).toBeBoolean();
});
- Для свойства
onHide.
it("render correct onHide prop type", () => {
const props = {
onHide: () => {}
},
ModalWrapperComponent = shallow(<ModalWrapper {...props} />).find(
"Modal"
);
expect(ModalWrapperComponent.props().onHide).toBeFunction();
});
- Для свойства
component.
it("render correct component prop type", () => {
const props = {
component: () => {}
},
ModalWrapperComponent = mount(<ModalWrapper {...props} />);
expect(ModalWrapperComponent.props().component).toBeFunction();
});
Полный список тестов: ModalWrapper.test.js
- Код для тестируемого компонента: ModalTrigger.js
ModalWrapper.js покрыт тестами, теперь нужно сделать то же самое для ModalTrigger.js.
Обзор компонента: он основан на переключении состояния, которое указывает на видимость модального окна. Если переключатель в значение false, модальное окно скрыто. Функция open() открывает модальное окно из дочернего компонента, сделайте клик по кнопке закрытия и функция close() скроет модальное окно.
2.1 Создадим снимок
it("render ModalTrigger component correctly", () => {
const ModalTriggerComponent = shallow(
<ModalTrigger>
<div />
</ModalTrigger>
);
expect(ModalTriggerComponent).toMatchSnapshot();
});
Должны ли мы тестировать в ModalTrigger.js отрисовку свойств компонента? Нет, поскольку эти компоненты будут отрисованы внутри компонента ModalWrapper.js. А это уже было покрыто тестами в компоненте ModalWrapper.js.
2.2 Тестирование свойств. У нас есть одно свойство children и мы хотим быть уверенны, что у нас есть только один дочерний элемент.
it("ensure to have only one child (control element)", () => {
expect(
ModalTriggerComponent.findWhere(node => node.key() === "modal-control")
.length
).toEqual(1);
});
2.3 Тестирование типов. children должен быть объектом, проверим это в следующем тесте:
const ModalTriggerComponent = mount(
<ModalTrigger>
<div />
</ModalTrigger>
);
it("check children prop type", () => {
expect(ModalTriggerComponent.props().children).toBeObject();
});
2.4 Важной частью компонента ModalTrigger.js является проверка состояний.
У нас два состояния:
- Модальное окно открыто. Чтобы узнать, что модальное окно открыто, нужно проверить его состояние. Для этого вызовите функцию
openиз экземпляра компонента и ожидать, что состояниеtoggledбудетtrue.
it("check the modal is opened", () => {
const event = {
preventDefault: () => {},
stopPropagation: () => {}
};
ModalTriggerComponent.instance().open(event);
expect(ModalTriggerComponent.state().toggled).toBeTruthy();
});
- Модальное окно закрыто. Тестируем обратную ситуацию, нужно чтобы
toggledбылfalse.
it("check the modal is closed", () => {
ModalTriggerComponent.instance().close();
expect(ModalTriggerComponent.state().toggled).toBeFalsy();
});
Полный список тестов: ModalTrigger.test.js
Теперь модальные окна полностью протестированы. Один совет для тестирования компонентов, который зависят друг от друга: сначала просмотрите компоненты и запишите план тестирования, определите, что вам нужно протестировать в каждом компоненте, проверьте тестовые примеры для каждого компонента и убедитесь, что вы не проверяете тот же тест в нескольких компонентах. Тщательно проанализируйте возможные и оптимальные варианты тестирования.
5. Тестирование HOC (компоненты высшего порядка)
Последние две части (тестирование HOC и полей формы) взаимосвязаны. Я бы хотела поделиться с вами, как правильно тестировать поля с HOC.
Объяснение того, что такое BaseFieldLayout, зачем нам нужен этот компонент и где мы его используем:
BaseFieldLayout— это обертка для компонентов ввода формы, таких какTextInput,CheckboxInput,DateInput,SelectInputи др. Их имена заканчиваются на-Input, потому что мы используем пакетredux-formи эти компоненты являются компонентами ввода в логикеredux-form.- Нам нужен
BaseFieldLayoutдля создание макетов для компонентов полей формы, то есть для отрисовки заголовков полей, всплывающих подсказок, префиксов (валют, аббревиатуры квадратных метров и т.д.), иконок, ошибок... - Мы используем его в
BaseFieldHOCдля обертыванияinputComponentв макет поля и подключения его кredux-formс помощью компонента<Field/>.
Код для тестируемого компонента: BaseFieldHOC.js
Анализ HOC:
- Этот компонент получает только одно свойство — компонент. Прежде всего нам нужно создать этот компонент и обернуть его в
BaseFieldHOC. - Далее, нам нужно декорировать обертку HOC с
redux-form, чтобы получить поле, связанное сredux-form. - Отрисовываем это поле внутри компонента
React Redux <Provider>, чтобы сделать хранилище доступным для тестируемого компонента. Чтобы имитировать хранилище, просто сделайте:
const store = createStore(() => ({}));
Теперь, перед каждым тестом, нужно сделать следующее:
let BaseFieldHOCComponent;
beforeEach(() => {
const TextInput = () => {
return "text input";
},
BaseFieldHOCWrapper = BaseFieldHOC(TextInput),
TextField = reduxForm({ form: "testForm" })(BaseFieldHOCWrapper);
BaseFieldHOCComponent = renderer
.create(
<Provider store={store}>
<TextField name="text-input" />
</Provider>
)
.toJSON();
});
После этого компонент готов к использованию:
- Создадим снимок
it("render correctly component", () => {
expect(BaseFieldHOCComponent).toMatchSnapshot();
});
- Убедимся, что компонент ввода обернут в
BaseFieldLayoutпосле отрисовки:
it("check input component is wrapped in BaseFieldLayout", () => {
expect(BaseFieldHOCComponent.props.className).toEqual("form-group");
});
Вот и все, HOC покрыт тестами. Самое сложное в тестировании, связанном с компонентами redux-form — это подготовка поля (декорировать redux-form и настроить хранилище), остальное просто — следуйте инструкциям и больше ничего.
Полный список тестов: BaseFieldHOC.test.js
6. Тестирование форм/полей
Поле HOC, было покрыто тестами, и мы можем перейти к компоненту BaseFieldLayout.
Код для тестируемого компонента: BaseFieldLayout.js
Давайте напишем тесты для BaseFieldLayout в соответствии с инструкциями выше:
- Прежде всего, создадим снимок.
Этот компонент не будет отрисован без defaultProps:
inputComponent.- Свойства предоставляемые
redux-form: объектыinputиmeta.inputсо свойствомnameиmetaсо свойствамиtouchedиerror.
const defaultProps = {
meta: {
touched: null,
error: null
},
input: {
name: "field-name"
},
inputComponent: () => {
return "test case";
}
};
Чтобы использовать defaultProps в каждой тестируемой обертке, выполним следующие действия:
import TestBaseFieldLayout from "../BaseFieldLayout";
const BaseFieldLayout = props => (
<TestBaseFieldLayout {...defaultProps} {...props} />
);
Теперь мы готовы создать снимок:
it("render correctly BaseFieldLayout component", () => {
const BaseFieldLayoutComponent = renderer
.create(<BaseFieldLayout />)
.toJSON();
expect(BaseFieldLayoutComponent).toMatchSnapshot();
});
- Тестирование свойств
Этот компонент имеет множество свойств. Я покажу пример нескольких, остальные тестируются по аналогии.
- Убедимся, что свойство
iconкорректно используется.
it("render correctly icon prop", () => {
const props = {
icon: <span className="icon-exclamation" />
},
BaseFieldLayoutComponent = mount(<BaseFieldLayout {...props} />);
expect(
BaseFieldLayoutComponent.find("span").hasClass("icon-exclamation")
).toBeTruthy();
});
- Убедимся, что свойство
tooltipкорректно используется.
const props = {
labelTooltipContent: "tooltip for label"
},
BaseFieldLayoutComponent = mount(<BaseFieldLayout {...props} />);
it("check prop is rendered", () => {
expect(
BaseFieldLayoutComponent.find("span").hasClass("tooltip-icon")
).toBeTruthy();
});
- Тестирование
fieldLinkсвойства, убедимся, чтоfieldLinkимеет значениеnullпо умолчанию.
it("check prop is null by default", () => {
const BaseFieldLayoutComponent = shallow(<BaseFieldLayout />);
expect(BaseFieldLayoutComponent.props().fieldLink).toBe(null);
});
- Тестирование ошибок
it("check if field has error", () => {
const props = {
meta: {
touched: true,
error: "This field is required"
}
},
BaseFieldLayoutComponent = mount(<BaseFieldLayout {...props} />);
expect(BaseFieldLayoutComponent.find(".error")).toHaveLength(1);
});
Полный список тестов: BaseFieldLayout.test.js
Подведем итоги
Теперь у вас есть подробное руководство о том, как выполнять полное тестирование компонентов на основе структуры проекта. По собственному опыту я попыталась объяснить, что и в каком порядке необходимо проверить, а что можно опустить при тестировании. Кроме того, я продемонстрировала примеры тестирования нескольких компонентов и определила последовательность покрытия кодовой базы. Я надеюсь, что вам будет полезна эта статья. Спасибо за чтение.
Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.
메타데이터
- post_id
- d3504f3fbc54
- slug
- what-and-how-to-test-with-jest-and-enzyme-full-instruction-on-react-components-testing-d3504f3fbc54
- url
- https://medium.com/devschacht/what-and-how-to-test-with-jest-and-enzyme-full-instruction-on-react-components-testing-d3504f3fbc54
- canonical_url
- https://medium.com/devschacht/what-and-how-to-test-with-jest-and-enzyme-full-instruction-on-react-components-testing-d3504f3fbc54
- author_url
- https://medium.com/@alexandrshy
- status
- ok
- fetched_at
- 2026-08-21 23:18:53