Я перешел с 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 он перестал работать.
Мои текущие версии зависимостей:
я пробовал это
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 />
@DrewReese Обновлено. Пожалуйста, смотрите часть псевдокода для того, что я хочу протестировать. Я получил сообщение об ошибке при использовании MemoryRouter
(см. конец)
Я не думаю, что вы правильно думаете о «единице», как при тестировании наименьшей необходимой единицы кода. Если вы пытаетесь протестировать компонент Post
, попробуйте рендерить только Post
. Опять же, это зависит от того, что вы пытаетесь протестировать. Иногда некоторые компоненты необходимо отображать в контексте маршрутизации, поэтому для предоставления контекста необходим маршрутизатор.
FWIW тест для перехода с одной страницы на другую не является модульным тестом, он больше граничит с интеграционным тестированием (то есть, как две или более единицы кода интегрируются вместе). react-testing-library
не подходит для работы по интеграционному тестированию. Для этого ищите что-то вроде puppeteer, selenium, cypress и т. д.
@DrewReese Я думаю, что это своего рода модульный тест - проверка только логики маршрутизации. См. этот документ , который тестирует /home to /about навигацию по страницам. Модульное тестирование нажатием кнопки для открытия панели не так уж отличается от модульного тестирования нажатием ссылки для перехода на другую страницу. Кроме того, мой модульный тест работал для React Router V5, следуя этому документу. Он просто сломался после перехода на V6.
Если вы хотите протестировать конфигурацию ваших маршрутов в целом, используя новые [email protected]
маршрутизаторы данных, я бы предложил немного рефакторить код, чтобы можно было заглушить 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
, когда я хочу проверить текущий путь или выполнить поиск.
Кстати, testing-library.com/docs/example-react-router, вероятно, следует обновить это. :D
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");
});
Что вы пытаетесь протестировать? Вы должны тестировать блоки своего кода, а не стороннего кода. Что может помешать вам обернуть компонент
Posts
вMemoryRouter
и протестироватьPosts
поведение, например?