I've configured React Router in index.js
as follows...
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./index.css";
import App from "./App";
import ErrorPage from "./components/ErrorPage/ErrorPage";
import store from "./store";
// https://reactrouter.com/en/main/routers/create-browser-router
export const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
},
]);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
</React.StrictMode>
);
In setupTests.js
, I'm using a custom renderWithProviders()
function to wrap around React testing Library's render function, as described in https://testing-library.com/docs/react-testing-library/setup/#custom-render:
import React from "react";
import "@testing-library/jest-dom";
import { render } from "@testing-library/react";
import { Provider } from "react-redux";
import { RouterProvider } from "react-router-dom";
// import { router } from "../index"; // This is causing the test error
import { server } from "./mocks/server";
import { setupStore } from "../store";
// Establish API mocking before all tests
beforeAll(() => server.listen());
// Reset any request handlers after each test
afterEach(() => server.resetHandlers());
// Clean up after all tests are finished
afterAll(() => server.close());
export function renderWithProviders(
ui,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
useRouter = false,
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return useRouter ?
(
<Provider store={store}>
<RouterProvider router={router}>
{children}
</RouterProvider>
</Provider>
) : <Provider store={store}>{children}</Provider>;
}
// Return an object with the store and all of React Testing Library's query functions
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}
And finally, I have the following in App.test.js
:
import { MemoryRouter } from "react-router-dom";
import { screen } from "@testing-library/react";
import App from "./App";
import { renderWithProviders } from "./testSetup/setupTests";
// import { router } from "./index"; // Breaking tests here too
test('App dummy content is rendered', () => {
renderWithProviders(<App />); // Breaking despite not using the router
expect(screen.getByText("Hello World!")).toBeInTheDocument();
});
// This is what I wanted to test using the index.js router config
// test('An invalid path returns an error page', () => {
// renderWithProviders(
// <MemoryRouter initialEntries={["/invalid-path"]}>
// <App />
// </MemoryRouter>
// );
// expect(screen.getByText("Sorry, an unexpected error has occurred!")).toBeInTheDocument();
// });
This all works fine without importing router
from index.js, and has done since before I started trying to configure React Router within my test setup.
PASS src/App.test.js
√ App dummy content is rendered (47 ms)
However, as soon as I uncomment the router import in either setupTests.js
or in App.test.js
...
import { router } from "../index";
...I receive the following error when running npm test
:
FAIL src/App.test.js
● Test suite failed to run
createRoot(...): Target container is not a DOM element.
18 | ]);
19 |
> 20 | const root = ReactDOM.createRoot(document.getElementById('root'));
| ^
21 | root.render(
22 | <React.StrictMode>
23 | <Provider store={store}>
at createRoot (node_modules/react-dom/cjs/react-dom.development.js:29345:11)
at Object.createRoot$1 [as createRoot] (node_modules/react-dom/cjs/react-dom.development.js:29800:10)
at Object.createRoot (node_modules/react-dom/client.js:12:16)
at Object.<anonymous> (src/index.js:20:23)
at Object.<anonymous> (src/App.test.js:6:1)
If I re-comment the import(s), the test works again.
The strange thing is, the import isn't even being referenced in App.test.js
; just importing it causes the issue.
I also tried renaming the router variable and associated imports to appRouter
in case there was a name collission, but that didn't change anything.
I also tried importing my own constant from index.js
, which resulted in the same error, so it seems to be the nature of importing from that file (possibly before the route is rendered) which is the issue.
export const categoryListings = categories.map(category => {
return {
path: "categories/" + category.path,
element: <PostListing name={category.name} />,
}
});
The entire respository is here if that's useful: https://github.com/Cmastris/redducational/
Anyone have any ideas what's going on, and preferably how to fix it? Thanks!
I'm pleased to report that I managed to get this working, although I'm still not sure precisely what the original issue was. Sharing my solution for anyone else who has a similar issue...
I noticed that importing another constant from index.js
resulted in the same error, so it seems to be the nature of exporting/importing from index.js
(possibly related to exporting/importing before the route is rendered) which is causing the issue.
I moved all of my routing config to routing.js
...
import { createBrowserRouter } from "react-router-dom";
import { categories } from "./data/categories";
import App from "./App/App";
import ErrorPage from "./components/ErrorPage/ErrorPage";
import PostListing from "./features/postListings/PostListing";
export const categoryListingRoutes = categories.map(category => {
return {
path: "categories/" + category.path,
element: <PostListing name={category.name} />,
}
});
export const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{
path: "",
element: <PostListing name="All" />,
},
...categoryListingRoutes
],
},
]);
...leaving just the RouterProvider
in index.js
...
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { RouterProvider } from "react-router-dom";
import { router } from "./routing";
import store from "./store";
import "./index.css";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
</React.StrictMode>
);
Then, based on @Drew Reese's suggestion to use MemoryRouter
, I used createMemoryRouter()
together with my categories routing config to test the routing. No import-related errors this time, and the test is passing:
test('Category listing routing', () => {
const router = createMemoryRouter(categoryListingRoutes, {
initialEntries: ["/categories/science"]
});
renderWithProviders(<RouterProvider router={router} />);
expect(screen.getByText("Science")).toBeInTheDocument();
});