34
"react-router-dom": "^6.0.0-alpha.5",

I have tried nearly everything.

I just want to mock this navigate() call from the useNavigate() hook. that's it. Simple. Nothing working.

No, i do not want to use Link. useNavigate is used programmatically in other places as well and I want to mock them too

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

export const Detail = () => {
    const navigate = useNavigate();
    return (
        <span onClick={() => navigate('/some/specific/route')}>
            some Text
        </span>
    )
}

I have tried these:

jest.mock('react-router-dom', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('react-router-dom');

    return {
        __esModule: true,
        ...originalModule,
        // add your noops here
        useNavigate: jest.fn(() => 'bar')
    };
});
import * as ReactRouterDom from "react-router-dom";
...
// cannot redefine property
          Object.defineProperty(ReactRouterDom, 'useNavigate', {
              configurable: true,
              value: jest.fn(() => 'bar')
          });
// doesnt work
          jest.mock('react-router-dom', () => ({
              useNavigate: jest.fn(() => jest.fn),
          }))
// doesnt work
jest.spyOn(ReactRouterDom, 'useNavigate', 'get').mockReturnValue(jest.fn(() => jest.fn));
// doesnt work
jest.spyOn(ReactRouterDom, 'useNavigate').mockReturnValue(jest.fn(() => jest.fn));
// doesnt work
const mockedUsedNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
   ...jest.requireActual('react-router-dom') as any,
  useNavigate: () => mockedUsedNavigate,
}));

all of these either show "Cannot redefine Property 'useNavigate'", or that useNavigate() may be used only in the context of a <Router> component.

Seriously, any other import mock works fine.

What am I doing wrong?

MY MINIMUM RECREATED PROJECT: https://github.com/zacharytyhacz/useNavigateBug

Zac
  • 1,719
  • 3
  • 27
  • 48

4 Answers4

74

I had a similar concern that was fixed with this issue from react router

I would suggest you change the mock as is:

// pay attention to write it at the top level of your file
const mockedUsedNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
   ...jest.requireActual('react-router-dom') as any,
  useNavigate: () => mockedUsedNavigate,
}));


// your describe/it/test blocks go down there

exaucae
  • 2,071
  • 1
  • 14
  • 24
  • What error did it throw? Can you point to your example repo or show your test file? – exaucae Apr 22 '21 at 06:15
  • it says same error in original post `useNavigate() may be used only in the context of a component.` – Zac Apr 22 '21 at 13:48
  • Mind pasting your test file? – exaucae Apr 22 '21 at 16:19
  • Here's my minimal project of the issue: https://github.com/zacharytyhacz/useNavigateBug – Zac Apr 23 '21 at 22:35
  • 4
    Your mokedUseNavigate and jest mock(https://github.com/zacharytyhacz/useNavigateBug/blob/86ac42296a07af65f15ca1298817add5ac486310/tests/Home.test.tsx#L63) should be at the top level of the file, not inside "test", "it" or "describe": See this for reference: https://github.com/ReactTraining/react-router/issues/7811 – exaucae Apr 24 '21 at 09:55
  • can you edit your answer / post new answer mentiong that?, I gotchu on this bounty – Zac Apr 24 '21 at 15:27
  • 1
    @Zac please try render() – jay rangras Feb 16 '22 at 08:11
  • Working, THX a lot. – Andre Aus B Mar 22 '22 at 08:13
  • Why does the `jest.mock('react-router-dom'...` have to be at the top level of the file? – technogeek1995 Nov 04 '22 at 19:04
  • 1
    I already have that implementation but I can't make the navigate mock to be called. Here it is all the [details of my issue](https://stackoverflow.com/questions/75478724/usenavigate-from-react-router-dom-not-called-when-testing-with-jest). Please help. Thanks in advance. – manou Feb 17 '23 at 21:56
20

Here's how I mock and assert on navigate() calls in my component tests:

import * as router from 'react-router'

const navigate = jest.fn()

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

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

Paweł Gościcki
  • 9,066
  • 5
  • 70
  • 81
  • Testing implementation is only a workaround and is advised against mostly, but since I can't make it work with the accepted answer, I'll have to use such approach... – Enfield Li Nov 21 '22 at 23:06
  • @Enfieldli totally agree. However, there are situations where your component might do a redirect via `navigate()` when it receives certain prop(s). For such situations asserting on a call to `navigate()` in a unit test is totally fine, imo. On the other hand, for navigation events done via clicking, testing it a higher level (via cypress, for example) is advised. – Paweł Gościcki Nov 22 '22 at 08:07
6

You should be able to spy on useNavigate normally if you change your import from:

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

to

import { useNavigate } from "react-router"
sschilli
  • 2,021
  • 1
  • 13
  • 33
  • This is the only thing that worked for me. Otherwise I was blocked by `TypeError: Cannot redefine property: useNavigate at Function.defineProperty ()` – Luke Jul 27 '23 at 18:41
1

I'm using React testing Library. Per the documentation, I have a test utils file which exports a custom render method. The custom render method wraps the rendered component in mocked providers as well as a router. This enables me to do the following:

 it('redirects when span is clicked', async () => {
    const span = await screen.findByText('some text')
    await userEvent.click(span)
    await waitFor(() => expect(window.location.href).toContain('/some/sepcific/route'))
  })

To be fair, it's a different approach than what the OP asked specifically about. However, I think it's worth mentioning.

Neil Girardi
  • 4,533
  • 1
  • 28
  • 45