Maxime Côté
Maxime Côté

Reputation: 381

Mocking react-router-dom hooks using jest is not working

I'm using Enzyme's shallow method to test a component which uses the useParams hook to get an ID from the URL params.

I'm trying to mock the useParams hook so that it does't call the actual method, but it doesn't work. I'm still getting TypeError: Cannot read property 'match' of undefined, so it calls the actual useParams, and not my mock.

My component:

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

export default () => {

  const { id } = useParams();

  return <div>{id}</div>;

};

Test:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import Header from './header';
import { shallow } from 'enzyme';

Enzyme.configure({ adapter: new Adapter() });

describe('<Header />', () => {

  jest.mock('react-router-dom', () => ({
    useParams: jest.fn().mockReturnValue({ id: '123' }),
  }));

  it('renders', () => {
    const wrapper = shallow(<Header />);
    expect(wrapper).toBeTruthy();
  });

});

Thank you!

Upvotes: 38

Views: 39598

Answers (11)

Raza
Raza

Reputation: 3383

I have this in my setupTests.js

export const mockHistoryPush = jest.fn();
export const mockHistoryGoBack = jest.fn();
export const mockHistoryReplace = jest.fn();
export const mockLocation = jest.fn();
export let mockUseParams = jest.fn();
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    push: mockHistoryPush,
    replace: mockHistoryReplace,
    goBack: mockHistoryGoBack,
  }),
  useLocation: () => mockLocation,
  useParams: mockUseParams,
}));

and when I want to use it and change it per test, I do

mockUseParams.mockReturnValue({id: 42});

in each of the tests.

Upvotes: 0

L Becker
L Becker

Reputation: 755

I had the same issue and this is what worked for me:

let mockUseParams;
jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useParams: jest.fn(() => mockUseParams),
}));

beforeEach(() => mockUseParams = undefined;)

test('MyComponent uses a param', () => {
    mockUseParams = { id: 'testId' };
    render(<MyComponent />);
});

Upvotes: 0

Alrin A Jabin
Alrin A Jabin

Reputation: 1

I had the same issue. I mocked useParams like this before describe

const mockedNavigate = jest.fn();

jest.mock("react-router-dom", () => ({
  ...(jest.requireActual("react-router-dom") as any),
  useNavigate: () => mockedNavigate,
  useParams: () => ({
    id: 223250 
  })
}));

Upvotes: 0

backslashN
backslashN

Reputation: 2875

You might be missing to add other keys of react-router-dom as is.

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useParams: jest.fn().mockReturnValue({ id: '123' })
}));

Upvotes: 1

mojave
mojave

Reputation: 435

I had the same issue. I mocked useParams like this:

jest.mock('react-router-dom', () => {
  return {
    useParams: () => ({
      id: '123'
    })
  }
})

Upvotes: 0

Matt H.
Matt H.

Reputation: 401

This works for me to mock useParams and change values for each unit test within the same file:

import React from "react";
import { render } from "@testing-library/react";
import Router from "react-router-dom";
import Component from "./Component";

jest.mock("react-router-dom", () => ({
 ...jest.requireActual("react-router-dom"),
 useParams: jest.fn(),
}));

const createWrapper = () => {
 return render(<Cases />);
};

describe("Component Page", () => {
 describe("Rendering", () => {
   it("should render cases container", () => {
     jest.spyOn(Router, 'useParams').mockReturnValue({ id: '1234' })
     const wrapper = createWrapper();
     expect(wrapper).toMatchSnapshot();
   });

   it("should render details container", () => {
     jest.spyOn(Router, 'useParams').mockReturnValue({ id: '5678' })
     const wrapper = createWrapper();
     expect(wrapper).toMatchSnapshot();
   });
 });
});

Just declare useParams as jest.fn() outside describe() and then change its values in each unit test with jest.spyOn

Upvotes: 37

Ankita Srivastava
Ankita Srivastava

Reputation: 325

For me mocking react-router-dom fix the issue:

jest.mock('react-router-dom', () => ({
    useParams: jest.fn().mockReturnValue({ nifUuid: 'nif123' }),
    useHistory: jest.fn()
}));

Upvotes: 0

Vity
Vity

Reputation: 91

I've had a similar problem, I solved it like this:

import { Route, Router } from "react-router-dom";
import { createMemoryHistory } from "history";

const renderWithRouter = (component) => {
  const history = createMemoryHistory({
    initialEntries: ["/part1/idValue1/part2/idValue2/part3"],
  });
  const Wrapper = ({ children }) => (
    <Router history={history}>
      <Route path="/part1/:id1/part2/:id2/part3">{children}</Route>
    </Router>
  );
  return {
    ...render(component, { wrapper: Wrapper }),
    history,
  };
};

describe("test", () => {
  it("test desc", async () => {
    const { getByText } = renderWithRouter(<MyComponent/>);
    expect(getByText("idValue1")).toBeTruthy();
  });
});

Upvotes: 9

Denis Chaikin
Denis Chaikin

Reputation: 1

I had the same issue. Calling the "cleanup" function from the "@testing-library/react" helps me:

import { cleanup } from '@testing-library/react';

afterEach(() => {
    cleanup();
});

Upvotes: -1

Diego D&#237;az
Diego D&#237;az

Reputation: 489

I tried this mock but it doesn't work to me. Error: Cannot read property 'match' of undefined. It seems the component is not inside a router so it cannot mock the match with params. It works to me:

import { MemoryRouter, Route } from 'react-router-dom';

const RenderWithRouter = ({ children }) => (
  <MemoryRouter initialEntries={['uri/Ineed']}>
    <Route path="route/Ineed/:paramId">{children}</Route>
  </MemoryRouter>
);
const tf = new TestFramework();
describe('<MyComponent />', () => {
  tf.init({ title: 'Some test' }, props =>
    shallow(
      <RenderWithRouter>
        <MyComponent {...props} />
      </RenderWithRouter>
    )
  );

  it('Some description', () => {
    const wrapper = tf.render().html();
    expect(wrapper).toContain('something');
  });
});

Upvotes: 4

Ivan
Ivan

Reputation: 883

I am not sure why, also couldn't find it in the docs of react-router library, but changing react-router-dom to react-router in both tests and implementation worked for me.

So it becomes something like this:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import Header from './header';
import { shallow } from 'enzyme';

Enzyme.configure({ adapter: new Adapter() });

describe('<Header />', () => {

  jest.mock('react-router', () => ({
    useParams: jest.fn().mockReturnValue({ id: '123' }),
  }));

  it('renders', () => {
    const wrapper = shallow(<Header />);
    expect(wrapper).toBeTruthy();
  });

});

Upvotes: 9

Related Questions