Этот пост написан инженером Wave Марсело Лотифом и впервые был опубликован на freeCodeCamp.

Пару раз меня укусил плохой рефакторинг и сломанное приложение - даже когда все мои тесты были зелеными - я начал исследовать интеграционные тесты в React. Возможно также с Redux и React Router.

К моему абсолютному шоку, я не смог найти там хорошего материала. Те, что я обнаружил, либо выполняли неполные интеграционные тесты, либо просто выполняли их неправильно.

Итак, здесь мы собираемся создать интеграционный тест, который инициализирует компонент React, запускает моделирование взаимодействия с пользователем и утверждает, что наш компонент меняет то, как мы ожидаем его изменения.

О чем это не: модульное тестирование. Я не собираюсь сейчас углубляться в это, но есть очень веская причина, по которой мы в Волне (кстати, мы нанимаем!) Замедляем наши модульные тесты и переключаемся на интеграционные тесты. Прокрутите вниз, если вам это интересно.

Раскрытие информации: у меня бы эти тесты не работали так гладко, как сейчас, если бы не великие специалисты по интерфейсу в Wave, особенно замечательный Томми Ли, который придумал, как подключить маршрутизатор, так что спасибо !

Настройка

В этом проекте мы собираемся использовать React, Redux, React / Redux Router (необязательно) и Thunk (необязательно) для запуска приложения, Jest и Enzyme для тестирования.

Я пропущу настройку всего этого, так как есть много отличных руководств по этому поводу.

Чтобы настроить основы моего интеграционного теста, я собираюсь немного схитрить и создать служебную функцию с некоторым шаблонным кодом:

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

В вашем тестовом файле вам сначала нужно будет импортировать некоторые зависимости, ваш редуктор и ваш компонент:

Затем в функции beforeEach настройте переменные интеграционного теста, используя эту служебную функцию:

(Если вы не используете React Router или Thunk, вы можете просто удалить их ссылки здесь и в функции util, и все будет работать так же.)

Теперь у вас все готово, чтобы смонтировать компонент и протестировать его. Представим, что этот компонент отображает div, который отображает текст, поступающий от редуктора. При нажатии на нее текст должен измениться на другую строку, скажем, «новый текст». Чтобы проверить это взаимодействие, вы можете просто сделать:

Вот и все ☺ С помощью этого очень простого кода вы тестируете div, вызывающий производитель действий при нажатии, который отправляет действие редуктору, который изменяет данные, вызывая повторный рендеринг компонента, ожидается, что это изменится так, как вы хотите. Если какой-либо из этих шагов завершится неудачно, ваш тест станет красным, и вы знаете, что функции вашего приложения не работают.

Вы можете попытаться углубиться в эту цепочку и заявить о некоторых других вещах:

Тестирование вызовов API

В реальном мире вам, вероятно, потребуется вызвать некоторые API-интерфейсы для получения данных для вашего приложения, и это та часть, которую вам нужно смоделировать, чтобы эффективно протестировать вещи. Здесь мы воспользуемся Jest, это не лучший способ имитировать http-запросы, но я сделаю это для удобства.

Предполагая, что вы используете гипотетический http-клиент для вызова конечной точки через его функцию get при нажатии на div, затем установите возврат этого вызова в редуктор, который отображается обратно в div:

В еще более реальном приложении эта функция get вернет вам объект Promise. Отсюда все становится немного сложнее, потому что имитируемая функция щелчка не знает об этом обещании, и нет возможности выполнить свою функцию then. Ссылка на объект утеряна.

Нам нужно будет как-то дождаться выполнения этого обещания, прежде чем выполнять утверждения. Мы работаем над этим, немного взломав служебную функцию:

Теперь наш тест будет выглядеть так:

С оператором async… await, доступным с ES7, наш тест будет ждать, пока все обещания не будут разрешены, чтобы он мог делать свои утверждения. В настоящее время у Jest нет решения для этого, но этот прием довольно хорошо работает в реальной жизни.

Если у вас есть более сложные производители действий с другими обещаниями, вызываемыми в resolve или reject этого первого обещания, я предлагаю вам выполнить модульное тестирование этих вызовов, а также проверить окончательные результаты все кейсы в интеграционных тестах.

Больше тестирования

Если вам нужно установить начальное состояние для вашего компонента, вы можете отправлять действия вручную, пока не достигнете желаемого состояния:

store.dispatch({ payload: 'data', type: 'SOME_ACTION' });

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

Пожалуйста, оставьте свое мнение в разделе комментариев, здесь нужно внести много улучшений, и я рад изменить это в соответствии с вашими предложениями. Спасибо!

ДА НЕТ ТЕСТ БЛОКА?!?

Мы в Волне (я уже упоминал мы нанимаем?) Уже провели кучу фронтальных юнит-тестов, и, честно говоря, большинство из них оказались бесполезными. Конечно, они лежат в основе TDD, но некоторые модульные тесты редукторов и производителей действий представляют собой всего лишь шаблонный код и не добавляют особой ценности коду или процессу TDD.

На самом деле вы можете сделать действительно хороший TDD только с помощью интеграционных тестов, и они будут полезны в будущем, чтобы выявлять неработающие ссылки между слоями вашего приложения и, в конечном итоге, проверять, ведет ли ваше приложение ожидаемое поведение, для чего предназначены автоматические тесты.

Не поймите меня неправильно, мы по-прежнему имеем пограничные примеры модульных тестов, которые слишком сложны или утомительны для воспроизведения в интеграционных тестах, но большинство наших модульных тестов стали бесполезными, как только мы добавили интеграционные тесты, подобные приведенным выше. В конце концов, это означает, что время, которое мы сейчас тратим на обдумывание, разработку и исправление тестов, намного меньше, чем было раньше, и они намного эффективнее выявляют проблемы в приложении. Итак, выигрывай, выигрывай ☺

Вы можете столкнуться с одной проблемой, связанной с глубоким монтажом, а не с неглубоким рендерингом. Вы можете подумать, что некоторые деревья компонентов слишком сложны для монтирования, но я скажу, что еще одним преимуществом монтирования корневого компонента является проверка правильности создания экземпляров дочерних компонентов. Если вы подключили дочерние компоненты, вы можете протестировать их отдельно, если хотите. Я не пробовал выполнять поверхностный рендеринг подключенного компонента, чтобы проверить, работает ли эта установка для интеграционного тестирования, но вы можете попробовать. Если вы не хотите монтировать и не подключаете дочерние компоненты, я не исследовал еще одну возможность - это неглубокий рендеринг с последующим их соединением вручную. Здесь важно чувствовать себя комфортно с количеством и качеством тестов, которые вы пишете, и убедиться, что они действительно помогают в автоматическом выполнении некоторых регрессионных тестов и обнаружении скрытых проблем.