Testing useNavigate() / navigate() from react-router v6

Testing navigate() is slightly more problematic with the latest v6 (as of writing this post) react-router than just asserting on history.push() as it was the case in the previous versions. Let’s say we have this ButtonHome component:

import { useNavigate } from 'react-router-dom'

const ButtonHome = () => {
  const navigate = useNavigate()

  const onClick = () => navigate('/home')

  return (
    <button onClick={onClick}>
      Home
    </button>
  )
}

I would write a test for this component using the react-testing-library in the following way:

import * as router from 'react-router'
import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import ButtonHome from './ButtonHome'

describe('ButtonHome', () => {
  const ui = userEvent.setup()
  const navigate = jest.fn()

  beforeEach(() => {
    jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate)
  })

  it('renders the button and navigates to /home upon click', async () => {
    render(withRouter(<ButtonHome />))
    
    await ui.click(screen.queryByText('Home'))

    expect(navigate).toHaveBeenCalledWith('/home')
  })
})

The relevant bits just for testing the router are as follows:

import * as router from 'react-router'

const navigate = jest.fn()

beforeEach(() => {
  jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate)
})

it('...', () => {
  expect(navigate).toHaveBeenCalledWith('/path')
})

The test also requires the following withRouter() helper, which I have in jest.setup.js:

import { Route, Router, Routes } from 'react-router-dom'
import { createBrowserHistory } from 'history'

const history = createBrowserHistory()

const withRouter = (children, opts = {}) => {
  const { path, route } = opts

  if (path) {
    history.push(path)
  }

  return (
    <Router location={history.location} navigator={history}>
      <Routes>
        <Route
          path={route || path || '/'}
          element={children}
        />
      </Routes>
    </Router>
  )
}

global.withRouter = withRouter

Published by

Paweł Gościcki

Ruby/Rails programmer.