44

I have header component like below:

import { useLocation } from "react-router-dom";

const Header = () => {
   let route = useLocation().pathname; 
   return route === "/user" ? <ComponentA /> : <ComponentB />;
}

How will you mock this useLocation() to get the path as user?

I cant simply call the Header component as below in my test file as I am getting an error:

TypeError: Cannot read property 'location' of undefined at useLocation

describe("<Header/>", () => {
    it("call the header component", () => {
        const wrapper = shallow(<Header />);
        expect(wrapper.find(ComponentA)).toHaveLength(1);
    });
});

I have tried looking similar to the link How to test components using new react router hooks? but it didnt work.

I have tried like below:

const wrapper = shallow(
      <Provider store={store}>
        <MemoryRouter initialEntries={['/abc']}>
          <Switch>
            <AppRouter />
          </Switch>
        </MemoryRouter>
      </Provider>,
    );
    jestExpect(wrapper.find(AppRouter)
      .dive()
      .find(Route)
      .filter({path: '/abc'})
      .renderProp('render', { history: mockedHistory})
      .find(ContainerABC)
    ).toHaveLength(1);

from the link Testing react-router with Shallow rendering but it didnt work.

Please let me know.

Thanks in advance.

purmo037
  • 543
  • 1
  • 4
  • 7

6 Answers6

81

I found that I can mock the React Router hooks like useLocation using the following pattern:

import React from "react"
import ExampleComponent from "./ExampleComponent"
import { shallow } from "enzyme"

jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"),
  useLocation: () => ({
    pathname: "localhost:3000/example/path"
  })
}));

describe("<ExampleComponent />", () => {
  it("should render ExampleComponent", () => {
    shallow(<ExampleComponent/>);
  });
});

If you have a call to useLocation in your ExampleComponent the above pattern should allow you to shallow render the component in an Enzyme / Jest test without error.

starball
  • 20,030
  • 7
  • 43
  • 238
jacobedawson
  • 2,929
  • 25
  • 27
  • Thank you! I have not yet tried your solution but will let you know once after working on it. – purmo037 Feb 19 '20 at 12:23
  • 5
    I can confirm this works. You can also have this in `__mocks__/react-router-dom.js` and add it to `jest.setupFiles` in `package.json` to share among multiple tests. – Alf Mar 04 '20 at 16:48
  • But I have just tried this two days back. I am still getting the same error with shallow. – purmo037 Mar 06 '20 at 07:52
  • I don't know what I am doing wrong, I did this exact test, both with React shallow renderer and with enzyme, and when my component try getting the query parameters, it return null but the param has a value!!! – Gabriel Beauchemin-Dauphinais Jun 12 '20 at 22:07
  • Oh I finally got it, in my component I was using the search property and not the pathname, that was simply my problem. After providing my query parameters in the search property, this answer work perfectly, with enzyme and react test shallow. Thanks. – Gabriel Beauchemin-Dauphinais Jun 13 '20 at 02:12
  • And for anyone interested, it is important to have it outside of the `describe` block. Otherwise, it will not be overwritten. – vazsonyidl Mar 01 '23 at 14:41
23

I've been struggling with this recently too...

I found this works quite nicely:

import React from "react"
import ExampleComponent from "./ExampleComponent"
import { shallow } from "enzyme"

const mockUseLocationValue = {
    pathname: "/testroute",
    search: '',
    hash: '',
    state: null
}
jest.mock('react-router', () => ({
    ...jest.requireActual("react-router") as {},
    useLocation: jest.fn().mockImplementation(() => {
        return mockUseLocationValue;
    })
}));

describe("<ExampleComponent />", () => {
  it("should render ExampleComponent", () => {
    mockUseLocationValue.pathname = "test specific path";
    shallow(<ExampleComponent/>);
    ...
    expect(...
  });
});

this way, I was able to both mock useLocation and provide a value for pathname in specific tests as necessary.

HTH

John Sharp
  • 583
  • 5
  • 6
  • 4
    perfect, this should be the excepted answer. i would also suggest to reset the mocked location before each test case. – Hinrich Apr 29 '21 at 13:21
  • 2
    This results in `TypeError: Cannot read property 'pathname' of undefined` – Hiroki Nov 20 '21 at 15:23
  • 1
    Jest got confused by `as {}` (maybe my Babel is not set up properly with Jest), removed those and it worked well. Also require is from `react-router-dom` – Dmitry Shvedov Feb 14 '23 at 02:15
3

If you are using react-testing-library:

import React from 'react';
import { Router } from 'react-router-dom';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import Component from '../Component.jsx';

test('<Component> renders without crashing', () => {
    const history = createMemoryHistory();

    render(
        <Router history={history}>
            <Component />
        </Router>
    );
});

More info: https://testing-library.com/docs/example-react-router/

vljs
  • 978
  • 7
  • 15
2

I know this isn’t a direct answer to your question, but if what you want is to test the browser location or history, you can use mount and add an extra Route at the end where you can “capture” the history and location objects.

test(`Foobar`, () => {
  let testHistory
  let testLocation

  const wrapper = mount(
    <MemoryRouter initialEntries={[`/`]}>
      <MyRoutes />
      <Route
        path={`*`}
        render={routeProps => {
          testHistory = routeProps.history
          testLocation = routeProps.location
          return null
        }}/>
    </MemoryRouter>
  )

  // Manipulate wrapper

  expect(testHistory)...
  expect(testLocation)...
)}
Alf
  • 1,414
  • 1
  • 15
  • 27
  • Yes! It works with "mount" as you mentioned above. This is what I am using currently. I was just eager to know how it works with "shallow". Thanks! – purmo037 Mar 06 '20 at 07:55
0

Have you tried:

describe("<Header/>", () => {
    it("call the header component", () => {
        const wrapper = shallow(<MemoryRouter initialEntries={['/abc']}><Header /></MemoryRouter>);
        expect(wrapper.find(Header).dive().find(ComponentA)).toHaveLength(1);
    });
});

When you use shallow only the first lvl is rendered, so you need to use dive to render another component.

Kaca992
  • 2,211
  • 10
  • 14
  • Yes. I have tried but it is not working. Still getting the same error. – purmo037 Jan 29 '20 at 08:49
  • Won't this just render the router? `
    ` is a child of ``, and `shallow` renders just the top component, so If I have understood this correctly, `
    ` will not get rendered at all
    – Suppen May 19 '20 at 12:46
0

None of the solutions above worked for my use case(unit testing a custom hook). I had to override the inner properties of useLocation which was read-only.


\\ foo.ts

export const useFoo = () => {

   const {pathname} = useLocation();


\\ other logic

return ({
          \\ returns whatever thing here
       });
}

/*----------------------------------*/

\\ foo.test.ts

\\ other imports here

import * as ReactRouter from 'react-router';


Object.defineProperty(ReactRouter, 'useLocation', {
   value: jest.fn(),
   configurable: true,
   writable: true,
});

describe("useFoo", () => {


       it(' should do stgh that involves calling useLocation', () => {

           const mockLocation = {
               pathname: '/path',
               state: {},
               key: '',
               search: '',
               hash: ''
           };


         const useLocationSpy =  jest.spyOn(ReactRouter, 'useLocation').mockReturnValue(mockLocation)



          const {result} = renderHook(() => useFoo());
         
           expect(useLocationSpy).toHaveBeenCalled();


       });
 });

exaucae
  • 2,071
  • 1
  • 14
  • 24