I migrated from React Router v5 to v6 following this tutorial to create an app. I want to test it with react-testing-library, but my old unit tests (using the pattern in this doc) stopped working.
My app with React Router v6 is like this
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>
);
}
As you can see, it's using RouterProvider
instead of Switch
/Route
(so I'm confused that this SO question says it's using React Router v6 but it looks so different.).
The code in official doc of testing-library is not using RouterProvider
either.
I want to test some routing logic like this pseudo code:
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
How can I create a renderWithRouter
function like this with RouterProvider
? Note that this renderWithRouter
worked for me when I used React Router v5, but after migrating to v6, it stopped working.
My current dependency versions:
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-router-dom": "^6.4.3",
- "@testing-library/jest-dom": "^5.16.5",
- "@testing-library/react": "^13.4.0",
- "@testing-library/user-event": "^14.4.3",
I tried this
test("click post goes to /post/:postId", async () => {
render(
<MemoryRouter initialEntries={["/posts"]}>
<App />
</MemoryRouter>,
);
// ...
});
but I got error
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 />
CodePudding user response:
If you want to test your routes configuration as a whole, using the new [email protected]
Data Routers, then I'd suggest a bit of a refactor of the code to allow being able to stub in a MemoryRouter
for any unit testing.
Declare the routes configuration on its own and export.
const routesConfig = [
{
path: "/",
element: (
<>
<SiteHeader />
<Outlet />
</>
),
errorElement: <NotFound />,
children: [
{ path: "/", element: <Home /> },
{ path: "/posts", element: <Posts /> },
{ path: "/post/:postId", element: <PostPage /> },
],
},
];
export default routesConfig;
In the app code import routesConfig
and instantiate the BrowserRouter
the app uses.
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>
);
}
For unit tests import the routesConfig
and instantiate a 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...
});