reactjstestingreact-router

React testing library not work with react router v6


i start to learning react testing library a few days ago and i dont know how to make my tests works with react router v6. I have this two test below:

  describe('testing login page', () => {
  test('veryfy inputs and buttons in login page', () => {
    render(
      <MemoryRouter initialEntries={['/']}>
        <Routes>
          <Route path="/" element={<LoginPage />} />
        </Routes>
      </MemoryRouter>,
    );
    const userInput = screen.getByRole('textbox');
    expect(userInput).toBeInTheDocument();
  describe('testing Mine Page', () => {
  test('check table components', () => {
    render(
      <MemoryRouter initialEntries={['/mina']}>
        <Routes>
          <Route path="/mina" element={<MineTable />} />
        </Routes>
      </MemoryRouter>,
    );
    const pageTitle = screen.getByRole('heading', { name: /mina/i });
    expect(pageTitle).toBeInTheDocument();
  });

Only the first test works, the second don't render the page MineTable, keeps rendering the last page Login. It's like the history of the first test render remains. Someone can help me? I want to test every page component of my application.


Solution

  • Since it seems that MineTable uses a useNavigate hook it needs a routing context provided to it higher in the ReactTree that it is being rendered into.

    You only need to provide a routing context, there's no need to render routes and such. For this, you can wrap the component under test directly with the MemoryRouter.

    describe('testing Mine Page', () => {
      test('check table components', () => {
        render(
          <MemoryRouter>
            <MineTable />
          </MemoryRouter>,
        );
        const pageTitle = screen.getByRole('heading', { name: /mina/i });
        expect(pageTitle).toBeInTheDocument();
      });
    });
    

    In fact, this pattern of needing to provide context is so common that RTL has a wrapper option you can use to provider a wrapper component that provides all the contexts a component uses, i.e. routing, themes, redux, locale translations, etc...

    Example:

    const RouterWrapper = ({ children }) => (
      <MemoryRouter>
        {children}
      </MemoryRouter>
    );
    

    ...

    import { RouterWrapper } from '../path/to/RouterWrapper';
    
    describe('testing Mine Page', () => {
      test('check table components', () => {
        render(<MineTable />, { wrapper: RouterWrapper });
        const pageTitle = screen.getByRole('heading', { name: /mina/i });
        expect(pageTitle).toBeInTheDocument();
      });
    });
    

    See custom render for more details.