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

7

48

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.

Tan answered 28/1, 2020 at 12:44 Comment(0)
M
3

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)...
)}
Moreno answered 5/3, 2020 at 0:16 Comment(1)
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!Tan
F
82

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.

Frisby answered 11/2, 2020 at 16:47 Comment(6)
Thank you! I have not yet tried your solution but will let you know once after working on it.Tan
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.Moreno
But I have just tried this two days back. I am still getting the same error with shallow.Tan
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!!!Landlubber
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.Landlubber
And for anyone interested, it is important to have it outside of the describe block. Otherwise, it will not be overwritten.Deceive
C
26

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

Compossible answered 26/2, 2021 at 12:20 Comment(3)
perfect, this should be the excepted answer. i would also suggest to reset the mocked location before each test case.Centipoise
This results in TypeError: Cannot read property 'pathname' of undefinedGrimace
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-domHb
H
4

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/

Highchair answered 17/11, 2021 at 14:18 Comment(0)
M
3

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)...
)}
Moreno answered 5/3, 2020 at 0:16 Comment(1)
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!Tan
M
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.

Millner answered 28/1, 2020 at 17:19 Comment(2)
Yes. I have tried but it is not working. Still getting the same error.Tan
Won't this just render the router? <Header> is a child of <MemoryRouter>, and shallow renders just the top component, so If I have understood this correctly, <Header> will not get rendered at allLionize
S
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();


       });
 });

Steele answered 28/8, 2021 at 5:43 Comment(0)
S
0

One more way to mock the useLocation within the vitest (without dependency on components) is:

import { afterEach, describe, expect, test, vi } from 'vitest'
import type { Location } from '@remix-run/router'

afterEach(() => {
  vi.resetModules() // https://vitest.dev/api/vi#vi-resetmodules
})

let currentLocation: Location = {
  pathname: 'localhost:3000/example/path',
  hash: '',
  key: 'default',
  search: '',
  state: null,
}

vi.mock('react-router-dom', async (importOriginal) => {
  const mod = (await importOriginal()) as object

  return {
    ...mod,
    useLocation: () => currentLocation,
  }
})

describe('...', () => {
  test('...', () => {
    currentLocation = { pathname: '/foobar404' }

    // ... your tests ...
  })
})
Smallpox answered 22/3 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.