How do you mock useLocation() pathname using shallow test enzyme Reactjs?

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.

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

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. Hope that helps!

Solution 2

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

Solution 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/

Solution 4

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

Solution 5

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.

Solution 6

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();


       });
 });

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply