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

Наше приложение

Примечание: вы можете найти код этого проекта на GitHub. Он не получит никаких наград за дизайн. 😝

Для целей этой статьи мы представим себе веб-сайт ресторана, который выглядит так:

Обратите внимание, что маршрутизатор не включен в компонент приложения. Вместо этого я включил его в src / index.js. Исключив его из компонента приложения, мы можем использовать в наших тестах тестовый маршрутизатор, которым легче управлять.

Что делать, если использование тестового маршрутизатора кажется слишком искусственным? Если вы сомневаетесь в использовании другого маршрутизатора в тестах и ​​на производстве, возможно, вам стоит:

  • Включите Router в свой App компонент;
  • Всегда отображайте компонент App в своих тестах (никогда не используйте дочерние компоненты, такие как Locations);
  • Переходите на свои страницы в тестах, находя и нажимая ссылки на странице

Плюсы этого подхода: вам не нужно читать оставшуюся часть этого поста 🙃 (и ваша тестовая установка будет менее сложной). Минусы: нельзя сразу загрузить историю маршрутизации (текущую и предыдущие страницы) в тестовую настройку; вам нужно пройти через все взаимодействия с пользователем, чтобы построить историю.

Компонент "Локации"

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

<Route path="/locations" component={Locations} />

И конкретная страница для определенного идентификатора местоположения:

<Route path="/locations/:id" component={Locations} />

Компонент Locations использует useParams для получения параметра URL :id. Если параметр id является ложным, это означает, что маршрут был /locations без параметра, и компонент отображает список ссылок местоположения:

Если параметр id правдив, он отобразит информацию для этого конкретного местоположения:

Пример кода для компонента "Местоположение"

Включение контекста маршрутизатора при тестировании

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

Давайте проведем простой тест для нашего компонента Locations в Locations.test.js. Ничего страшного, просто увидев, что он отображается без ошибок:

Uh oh

Когда мы запускаем этот тест с Jest, мы получаем это уродство:

Проблема в том, что мы пытаемся использовать useParams вне маршрутизатора. Неудивительно, что Джест смущен.

Решение

К счастью, Testing Library позволяет легко адаптировать свою render функцию для обертывания любыми элементами пользовательского интерфейса, которые могут понадобиться - будь то провайдер React Router или любой другой провайдер (см. «Включение маршрутизатора и других провайдеров» ниже для рендеринга с несколькими провайдерами).

В документации по тестированию библиотеки React описывается, как создать собственный рендер, включающий оболочку. В нашем случае мы могли бы создать этот файл test-utils.jsx в нашем каталоге src:

Теперь, если мы импортируем renderscreen, и любой другой импорт библиотеки тестирования) из этого файла, а не из @testing-library/react, все наши визуализированные компоненты будут заключены в MemoryRouter перед визуализацией.

Примечание. В документации по React Router рекомендуется MemoryRouter для тестирования; вы можете увидеть другие параметры в остальной документации React Router).

Для нового и улучшенного Locations.test.js просто измените первую строку для импорта из модуля test-utils:

import { render } from "./test-utils";

Повторно запускаем тесты и вуаля!

Проверка параметров URL-адреса местоположения

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

Нам нужно будет обновить наш render в test-utils.jsx, чтобы принять начальный маршрут, который мы можем передать в MemoryRouter. Часть «Память» означает, что маршруты хранятся в памяти, а не в браузере.

Примечание: в этом случае нам нужен только один начальный маршрут; в других случаях вы можете захотеть передать целый массив исторических маршрутов (например, если вы тестировали аутентификацию, которая вернула пользователя на ссылающийся маршрут).

Использование функции для оболочки рендеринга

Когда мы не заботились об указании начальных маршрутов, можно было указать рендеринг wrapper как функцию MemoryRouter в test-utils.jsx:

render(ui, { wrapper: MemoryRouter, ...options });

Однако теперь мы хотим добавить опору к MemoryRouter, и все станет еще интереснее. Опора, которую мы хотим добавить, - initialEntries, как рекомендовано в документации Начало с определенных маршрутов для тестирования маршрутизатора React ».

Из-за этой опоры нам нужно будет создать новую функцию для значения wrapper. Значение wrapper всегда было функцией (в конце концов, MemoryRouter - это просто функциональный компонент), но теперь нам нужно покопаться и немного испачкать руки.

Функция для wrapper принимает, среди прочего, children. В Документации по настройке библиотеки тестирования React для пользовательского рендеринга показано, как использовать свойство children в wrapper функции arg. Этот код делает то же самое, что и наш предыдущий код:

Но теперь у нас есть еще простор для маневра.

Передача начальных записей в функцию-оболочку

Мы хотим иметь возможность передавать начальные записи параметрам функции render, например:

render(<App />, { initialRoutes: ["/locations/berkeley"] });

Затем нам нужно передать это MemoryRouterWithInitialRoutes функции, которую мы написали выше, как initialEntries prop.

Шаг 1. Определите initialRoutes в customRender

Важно, чтобы значение по умолчанию initialRoutes было равно ["/"], поскольку MemoryRouter выдает ошибки, если массив пуст. Мы можем позаботиться об этом по умолчанию в customRender (независимо от того, что массив параметров может содержать или не содержать) следующим образом:

Шаг 2. Передайте initialRoutes в MemoryRouterWithInitialRoutes функцию

Затем мы можем передать наш недавно определенный initialRoutes в MemoryRouterWithInitialRoutes (вместе с аргументами по умолчанию, чтобы функция все еще могла получить доступ к children):

Шаг 3. Используйте параметр initialRoutes в функции MemoryRouterWithInitialRoutes

и, наконец, MemoryRouterWithInitialRoutes может использовать initialRoutes:

Начальные маршруты в действии

Уф, это было много настроек. Хорошая новость в том, что использовать a в тестовом файле относительно просто. Давайте воспользуемся им, чтобы проверить правильность маршрута при переходе к "/locations/berkeley":

Здесь мы ищем заголовок Berkeley, который должен быть на "/locations/berkeley", и находим его!

Почему приложение, а не местоположение?

Вы можете спросить: почему в приведенных выше примерах отображается компонент App, а не компонент Locations? Оказывается, когда вы удаляете компоненты из компонента React Router Switch, у вас нет доступа к объекту match (который содержит параметры URL и другую информацию о маршруте).

Вы можете исправить это, используя useRouteMatch в Locations.jsx вместо useParams:

Однако я бы не рекомендовал это, поскольку, как правило, усложнять производственный код просто ради тестов - не лучшая практика.

Включая маршрутизатор и других провайдеров

Помните MemoryRouterWithInitialRoutes?

Его можно обновить, чтобы добавить столько поставщиков, сколько захотите. Например, если вы хотите добавить провайдера Redux и провайдера React Query:

Примечание: вам нужно будет создать store для Redux так же, как и для фактического провайдера (не включено в приведенный выше код). Хорошая новость в том, что вы также можете использовать этот временный тест store для настройки начального состояния ваших тестов.

Возможно, вы захотите обновить имя функции на этом этапе с MemoryRouterWithInitialRoutes до Providers. 🙃

Вывод

Надеюсь, этого достаточно, чтобы вы начали тестировать приложения, использующие React Router. Как видите, настройка не из простых! К счастью, когда у вас есть MemoryRouter упаковка для render, становится проще применять маршруты в тестовых функциях.

Вопросы? Пожалуйста, спрашивайте в комментариях! Если ваш вопрос касается чего-то, что не работает с вашим тестовым кодом, гораздо больше шансов, что я смогу помочь, если вы включите ссылку на простой репозиторий GitHub, в котором воспроизводится проблема.