7

I just started to migrate from jest to vitest after migrating my app from cra to vite. I ran ainto an issue where I want to mock useParam hook of react-router-dom

Original code:

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'), // use actual for all non-hook parts
  useParams: () => ({
    taskId: 123,
  }),
}));

I tried something like this:

vi.mock('react-router-dom', async () => ({
  ...vi.importActual('react-router-dom'), // use actual for all non-hook parts
  useParams: () => ({
    taskId: 123,
  }),
}));

But it's not working?

Aaliyah
  • 71
  • 1
  • 3

6 Answers6

6

I had the same problem, but this works perfect for me with correct TypeScript types

import { Params } from 'react-router-dom';

vi.mock('react-router-dom', () => ({
      useParams: (): Readonly<Params<string>> => ({ taskId: 123 }),
}));
Lars Rijnen
  • 313
  • 1
  • 3
  • 13
  • This only works if I have a single test in the file. How can I mock the return value for multiple tests? – Borjante May 12 '23 at 12:30
  • @Borjante you can use a vi.mock in the before each and then it works. Or you can use spy on and run that before every test. – Lars Rijnen Jun 06 '23 at 09:40
2

There is an example of the cheatsheet on mocking part of a module. This is what worked for me:

vi.mock('react-router-dom', async () => {
  const mod = await vi.importActual('react-router-dom');
  return {
    ...mod,
    useParams: () => ({
      taskId: 123,
    }),
  };
});
cbaigorri
  • 2,467
  • 25
  • 26
1

You can create a __mocks__ folder with a react-router-dom mock as mentioned in the documentation: https://vitest.dev/api/vi.html#vi-mock. Then do the mocking like this:

__mocks__/react-router-dom.ts:

import { vi } from 'vitest';

export * from 'react-router-dom';

export const useParams = vi.fn();
// .. add more mocks if needed

Then in your *.test.tsx

import { useParams } from 'react-router-dom';

vi.mock('react-router-dom')

test("it mocks useParams()", () => {
  vi.mocked(useParams).mockReturnValue({ taskId: "123" });

  //...implement the tests

  expect(useParams).toHaveBeenCalled()
})
froston
  • 1,027
  • 1
  • 12
  • 24
0

You can also try something like:

vi.mock('react-router-dom', async () => {
  return {
    ...vi.importMock('react-router-dom'),
    useHistory: vi.fn(),
    useParams: vi.fn(),
    useLocation: () => ({
      search: '',
      pathname: '/',
    }),
    matchPath: vi.fn(),
    withRouter: vi.fn(),
    useRouteMatch: vi.fn(),
    Link: ({ children, to }: { children: JSX.Element; to: string }) =>
      React.createElement('a', { href: to }, children),
    Router: () => vi.fn(),
    HashRouter: () => vi.fn(),
    Switch: () => vi.fn(),
  };
});

I think the reason why it fails is because the test code imports the real react-router-dom and the vi.mock is called, which in turn mocks the react-router-dom.

Mocking should take place before running actual tests.

asdf
  • 49
  • 1
  • 5
  • Can you please explain how to mock a certain parameter which the component destructures from useParams()? I can't see it in your code. – Daniel Tkach Feb 04 '23 at 18:58
  • An idea would be to try to re-mock the useParams inside the specific test. Another approach is to parameterize useParams in the tested component and inject it with a mocked useParams in the test. – asdf Feb 05 '23 at 19:23
-1

Try something like this instead:

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

vi.mock('react-router-dom'

vi.mocked(useParams).mockReturnValue({ taskId: 123 })
Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
-1

Upgrading to react-router-dom 6 solved all my previous mocking issues with vitest.

Migrating from v5 to v6 should be straightforward.

asdf
  • 49
  • 1
  • 5