6

Using React Router DOM (v6) with React Testing Library, I would like to know if there's a way to deal with "mocked" history from createMemoryHistoryor an alternative like it.

Based on the example of Testing Library, I could mock the history on the Router component but in v6 this component doesn't accept the history prop anymore.

So, my doubt is: how can I test history and location with RTL (React Testing Library) with React Router DOM v6? Should I keep using the MemoryRouter?

Doskya
  • 61
  • 1
  • 2
  • RRDv6 rather tucks away and internalizes its own history reference, so it's not directly exposed via any of the higher level routers. You can create your own custom router to use a custom history object. – Drew Reese Dec 11 '21 at 08:38
  • Yep! As I saw in the codebase, apparently they don't have any way to access the history reference on the `Routers`. So I think that I need to write a custom router to deal with history object as you say. Thank you for the help! – Doskya Dec 12 '21 at 16:11
  • You may find this [answer](https://stackoverflow.com/a/70000286/8690857) helpful/useful. Cheers. – Drew Reese Dec 12 '21 at 18:53

2 Answers2

10

The recommended testing method using React Router DOM v6.4.0 relies on createMemoryRouter which will return a RemixRouter. Instead of using history through createMemoryHistory the state object of the Router from createMemoryRouter can be used since this Router uses its own history in memory. The state object contains location which we can check for proper navigation.

Using the RouterProvider from React Router DOM we can pass the router made by createMemoryRouter to the provider and along to our tests.

import { render, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {
  createMemoryRouter,
  RouterProvider,
  useNavigate,
} from 'react-router-dom'
import { screen } from '@testing-library/react'

// The element we want to render. Uses the hook useNavigate to send us somewhere.
const ElementToRender: React.FC = () => {
  const navigate = useNavigate()

  return <button onClick={() => navigate('/')}>Navigate to Home</button>
}

const setupMyTest = () => {
  const router = createMemoryRouter(
    [
      {
        path: '/',
        element: <>Navigated from Start</>,
      },
      {
        path: '/starting/path',
        // Render the component causing the navigate to '/'
        element: <ElementToRender />,
      },
    ],
    {
      // Set for where you want to start in the routes. Remember, KISS (Keep it simple, stupid) the routes.
      initialEntries: ['/starting/path'],
      // We don't need to explicitly set this, but it's nice to have.
      initialIndex: 0,
    }
  )

  render(<RouterProvider router={router} />)

  // Objectify the router so we can explicitly pull when calling setupMyTest
  return { router }
}

it('tests react router dom v6.4.0 navigates properly', async () => {
  const { router } = setupMyTest()

  // Given we do start where we want to start
  expect(router.state.location.pathname).toEqual('/starting/path')

  // Navigate away from /starting/path
  userEvent.click(screen.getByRole('button', { name: 'Navigate to Home' }))

  // Don't forget to await the update since not all actions are immediate
  await waitFor(() => {
    expect(router.state.location.pathname).toEqual('/')
  })
})

Agos
  • 18,542
  • 11
  • 56
  • 70
TheOverwatcher
  • 126
  • 1
  • 5
-4

Using react router dom v6, Router use navigator prop to accept history.

const history = createMemoryHistory(); 
...
<MockedProvider mocks={[]}>
  <Router navigator={history} location={"/"}>
    <Home />
  </Router>
</MockedProvider> 
... 
await waitFor(() => {
  expect(history.location.pathname).toBe("/");
});
Jur P
  • 103
  • 6