react jest mock useNavigate()
Asked Answered
F

4

44
"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

Fraya answered 19/2, 2021 at 20:3 Comment(0)
H
96

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

Hathcock answered 1/4, 2021 at 8:39 Comment(10)
What error did it throw? Can you point to your example repo or show your test file?Hathcock
it says same error in original post useNavigate() may be used only in the context of a <Router> component.Fraya
Mind pasting your test file?Hathcock
Here's my minimal project of the issue: github.com/zacharytyhacz/useNavigateBugFraya
Your mokedUseNavigate and jest mock(github.com/zacharytyhacz/useNavigateBug/blob/…) should be at the top level of the file, not inside "test", "it" or "describe": See this for reference: github.com/ReactTraining/react-router/issues/7811Hathcock
can you edit your answer / post new answer mentiong that?, I gotchu on this bountyFraya
@Fraya please try render(<BrowserRouter><YourComponent /></BrowserRouter>)Impractical
Working, THX a lot.Infield
Why does the jest.mock('react-router-dom'... have to be at the top level of the file?Rutty
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. Please help. Thanks in advance.Vorticella
L
32

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')
})

Lucinalucinda answered 18/5, 2022 at 12:47 Comment(2)
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...Barbate
@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.Harmaning
C
12

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"
Colourable answered 24/3, 2022 at 21:23 Comment(1)
This is the only thing that worked for me. Otherwise I was blocked by TypeError: Cannot redefine property: useNavigate at Function.defineProperty (<anonymous>)Hellcat
L
3

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.

Levy answered 30/4, 2023 at 15:24 Comment(1)
This answer saved me, at least for now.Uralaltaic

© 2022 - 2024 — McMap. All rights reserved.