-1

I am experiencing an issue after upgrading to React-Router version 7.6.2. I was able to replicate the failure: StackBlitz. Note that this test passed when I had v6 of React-Router-DOM.

The problem seems to be interacting with the react-router Link component in the test. While the test can find the link text in the rendered Brother component, invoking a click action should take us to a new route, i.e. "siblings/sister", and render a Sister component. It is worth noting that when the app is running navigating to "siblings/brother", clicking the link, properly loads "/siblings/sister" route and renders a Sister component. It is only the problem to replicate this behavior in the RTL test.

  1. Navigate to Stackblitz terminal, on macOS Ctrl-C to stop the app
  2. Run yarn test

So far my routing is configured:

<BrowserRouter>
  <Routes>
    <Route path="/siblings/*">
      <Route path="*" element={<Navigate to="brother" />} />
      <Route path="brother" element={<Brother />} />
      <Route path="sister" element={<Sister />} />s
    </Route>
  </Routes>
 </BrowserRouter>

The two components in question:

export const Brother = () => {
  return (
    <Link to="/siblings/sister">
      <span>i am a brother</span>
    </Link>
  );
};

export const Sister = () => {
  return <div>i am a sister</div>;
};

Also my current test:

import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { MemoryRouter, Route, Router, Routes } from 'react-router';
import '@testing-library/jest-dom';
import { App, Brother, LocationDisplay } from './App';

const renderConnected = (
  ui,
  { route = '', routeUrl = '', ...renderOptions } = {}
) => {
  // Makes sure the useLocation hook works in Menugrid header
  window.history.pushState({}, '', route);

  const Wrapper = ({ children }) => (
    <MemoryRouter initialEntries={[routeUrl]}>
      <Routes>
        <Route path={route} element={<div>{children}</div>} />
      </Routes>
    </MemoryRouter>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

test('full app rendering/navigating', async () => {
  const app = renderConnected(<Brother />, {
    route: '/siblings/*',
    routeUrl: '/siblings/*',
  });

  const singleLink = app.getByRole('link', { name: /i am a brother/i });

  // await userEvent.click(singleMenuLink);
  fireEvent.click(singleLink);
  expect(await app.findByText('i am a sister')).toBeInTheDocument();
});

And my package.json:

"dependencies": {
  "@testing-library/dom": "^10.4.0",
  "@testing-library/jest-dom": "^6.6.3",
  "@testing-library/react": "^16.3.0",
  "@testing-library/user-event": "^13.5.0",
  "react": "^18.3.1",
  "react-dom": "^18.3.1",
  "react-router": "^7.6.2",
  "react-scripts": "5.0.1",
  "web-vitals": "^2.1.4"
},
1
  • It's good that the problem is reproducible, but the question needs to contain all info to understand the problem without the need to navigate external link. Please, provide minimal code and explain the problem Commented Jun 12 at 10:07

1 Answer 1

1

Issues

  • The test case renders only one route: "/siblings/*".
  • The test case renders only one routed component: <Brother />. The <Sister /> is never mounted and rendered to be found in the DOM.
  • "/siblings/*" is not a URL path to match, it should be an absolute path, i.e. "/siblings/brother" or "/siblings/sister".

Solution Suggestion

Update renderConnected to render children routes within the Routes component.

const renderConnected = (ui, { routeUrl = '', ...renderOptions } = {}) => {
  // Makes sure the useLocation hook works in Menugrid header
  window.history.pushState({}, '', routeUrl);

  const Wrapper = ({ children }) => (
    <MemoryRouter initialEntries={[routeUrl]}>
      <Routes>{children}</Routes>
    </MemoryRouter>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

In the unit test case(s) pass the parent layout route (renders an Outlet by default) and nested routes and routed components (i.e. both Brother and Sister) you want to make assertions over. You should note this more closely resembles your actual use case with the BrowserRouter.

const app = renderConnected(
  <Route path="siblings">
    <Route path="brother" element={<Brother />} />,
    <Route path="sister" element={<Sister />} />,
  </Route>,
  {
    routeUrl: '/siblings/brother', // <-- current URL path
  }
);

Full code:

import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { MemoryRouter, Route, Router, Routes } from 'react-router';

import { App, Brother, Sister } from './App';

const renderConnected = (ui, { routeUrl = '', ...renderOptions } = {}) => {
  // Makes sure the useLocation hook works in Menugrid header
  window.history.pushState({}, '', routeUrl);

  const Wrapper = ({ children }) => (
    <MemoryRouter initialEntries={[routeUrl]}>
      <Routes>{children}</Routes>
    </MemoryRouter>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

test('full app rendering/navigating', async () => {
  renderConnected(
    <Route path="siblings">
      <Route path="brother" element={<Brother />} />,
      <Route path="sister" element={<Sister />} />,
    </Route>,
    {
      routeUrl: '/siblings/brother',
    }
  );

  const singleLink = screen.getByRole('link', { name: /i am a brother/i });

  // await userEvent.click(singleMenuLink);
  fireEvent.click(singleLink);
  expect(await screen.findByText('i am a sister')).toBeInTheDocument();
});
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.