Как протестировать логику маршрутизации с помощью React Router v6 и тестовой библиотеки?

Я перешел с React Router v5 на v6, следуя этому туториалу . Я хочу протестировать его с помощью react-testing-library , но мои старые модульные тесты (с использованием шаблона в этом документе) перестали работать.

Мое приложение с React Router v6 выглядит так

const router = createBrowserRouter([
    {
        path: "/",
        element: (
            <>
                <SiteHeader />
                <Outlet />
            </>
        ),
        errorElement: <NotFound />,
        children: [
            { path: "/", element: <Home /> },
            { path: "/posts", element: <Posts /> },
            { path: "/post/:postId", element: <PostPage /> },
        ],
    },
]);

function App() {
    return (
        <div className="app">
            <RouterProvider router={router} />
        </div>
    );
}

Как видите, он использует RouterProvider вместо Switch/Route (поэтому я сбит с толку тем, что в этом ТАК вопросе говорится, что он использует React Router v6, но он выглядит совсем по-другому.).

Код в официальном документе библиотеки тестирования тоже не использует RouterProvider.

Я хочу протестировать некоторую логику маршрутизации, подобную этому псевдокоду:

renderWithRouter(<App />, "/posts"); // loads /posts page initially
await user.click(screen.getByText("some post title")); // trigger click
expect(getUrl(location)).toEqual("/post/123"); // checks the URL changed correctly

Как я могу создать функцию renderWithRouter, такую ​​​​как this с RouterProvider? Обратите внимание, что этот renderWithRouter работал у меня, когда я использовал React Router v5, но после перехода на v6 он перестал работать.

Мои текущие версии зависимостей:

  • "реагировать": "^ 18.2.0",
  • "реагировать-дом": "^ 18.2.0",
  • "реакция-маршрутизатор-дом": "^ 6.4.3",
  • "@testing-library/jest-dom": "^5.16.5",
  • "@testing-library/реагировать": "^13.4.0",
  • "@testing-library/user-event": "^14.4.3",

я пробовал это

test("click post goes to /post/:postId", async () => {
    render(
        <MemoryRouter initialEntries={["/posts"]}>
            <App />
        </MemoryRouter>,
    );
    // ...
});

но я получил ошибку

You cannot render a <Router> inside another <Router>. You should never have more than one in your app.

      31 | test("click post goes to /post/:postId", async () => {
    > 32 |     render(
         |     ^
      34 |         <MemoryRouter initialEntries={["/posts"]}>
      36 |             <App />

Что вы пытаетесь протестировать? Вы должны тестировать блоки своего кода, а не стороннего кода. Что может помешать вам обернуть компонент Posts в MemoryRouter и протестировать Posts поведение, например?

Drew Reese 11.11.2022 08:57

@DrewReese Обновлено. Пожалуйста, смотрите часть псевдокода для того, что я хочу протестировать. Я получил сообщение об ошибке при использовании MemoryRouter (см. конец)

lzl124631x 11.11.2022 09:10

Я не думаю, что вы правильно думаете о «единице», как при тестировании наименьшей необходимой единицы кода. Если вы пытаетесь протестировать компонент Post, попробуйте рендерить только Post. Опять же, это зависит от того, что вы пытаетесь протестировать. Иногда некоторые компоненты необходимо отображать в контексте маршрутизации, поэтому для предоставления контекста необходим маршрутизатор.

Drew Reese 11.11.2022 09:13

FWIW тест для перехода с одной страницы на другую не является модульным тестом, он больше граничит с интеграционным тестированием (то есть, как две или более единицы кода интегрируются вместе). react-testing-library не подходит для работы по интеграционному тестированию. Для этого ищите что-то вроде puppeteer, selenium, cypress и т. д.

Drew Reese 11.11.2022 09:16
[LXI-SpartaCodingClub Full-Stack Bootcamp in Indonesia] 2023/1/21 TIL/Week 15]
[LXI-SpartaCodingClub Full-Stack Bootcamp in Indonesia] 2023/1/21 TIL/Week 15]
Я научился создавать карусель в ReactJS с помощью библиотеки Splide.
Краткое введение в Styled-компоненты
Краткое введение в Styled-компоненты
В настоящее время популярность Styled-компонентов становится все больше и больше. Большинство проектов, построенных на React.js, используют эту...
ДЕНЬ 8 | Страница обзора в React
ДЕНЬ 8 | Страница обзора в React
Сегодня я сделал страницу обзора с помощью react. Я использовал некоторые иконки из reacting icons и использовал генератор случайных чисел для...
Как настроить среду разработки React.
Как настроить среду разработки React.
весь процесс настройки среды разработки react.
Что вы должны знать о JSX - объяснение менее чем за 2 минуты
Что вы должны знать о JSX - объяснение менее чем за 2 минуты
React - это библиотека JavaScript для создания пользовательских интерфейсов.
Как использовать get bounds с react-leaflet и данными JSON
Как использовать get bounds с react-leaflet и данными JSON
Я считаю, что получить границы из библиотеки leaflet Js очень просто, но использование этой функции может быть немного сложным. Поэтому позвольте мне...
1
5
994
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если вы хотите протестировать конфигурацию ваших маршрутов в целом, используя новые react-router-dom@6.4 маршрутизаторы данных, я бы предложил немного рефакторить код, чтобы можно было заглушить MemoryRouter для любого модульного тестирования.

Объявить конфигурацию маршрутов самостоятельно и экспортировать.

const routesConfig = [
  {
    path: "/",
    element: (
      <>
        <SiteHeader />
        <Outlet />
      </>
    ),
    errorElement: <NotFound />,
    children: [
      { path: "/", element: <Home /> },
      { path: "/posts", element: <Posts /> },
      { path: "/post/:postId", element: <PostPage /> },
    ],
  },
];

export default routesConfig;

В коде приложения импортируйте routesConfig и создайте экземпляр BrowserRouter, который использует приложение.

import {
  RouterProvider,
  createBrowserRouter,
} from "react-router-dom";
import routesConfig from '../routes';

const router = createBrowserRouter(routesConfig);

function App() {
  return (
    <div className="app">
      <RouterProvider router={router} />
    </div>
  );
}

Для модульных тестов импортируйте routesConfig и создайте экземпляр MemoryRouter.

import {
  RouterProvider,
  createMemoryRouter,
} from "react-router-dom";
import { render, waitFor } from "@testing-library/react";
import routesConfig from '../routes';

...

test("click post goes to /post/:postId", async () => {
  const router = createMemoryRouter(routesConfig, {
    initialEntries: ["/posts"],
  });

  render(<RouterProvider router={router} />);

  // make assertions, await changes, etc...
});

Потрясающие. Спасибо! Это работает! С MemoryRouter мне нужно использовать router.state.location, когда я хочу проверить текущий путь или выполнить поиск.

lzl124631x 11.11.2022 10:11

Кстати, testing-library.com/docs/example-react-router, вероятно, следует обновить это. :D

lzl124631x 11.11.2022 10:15

FWIW, я создал свой собственный renderWithRouter для React Router V6.

export const renderWithRouter = (route = "/") => {
    window.history.pushState({}, "Test page", route);
    return {
        user: userEvent.setup(),
        ...render(<RouterProvider router={createBrowserRouter(routes)} />),
    };
};

А это примерный тест.

test("click Posts => shows Posts page", async () => {
    const { user } = renderWithRouter();
    const postsLink = screen.getByText("Posts").closest("a");
    expect(postsLink).not.toHaveClass("active");
    await user.click(postsLink as HTMLAnchorElement);
    expect(postsLink).toHaveClass("active");
    expect(getUrl(location)).toEqual("/posts");
});

Другие вопросы по теме