How to Test useLocation Hook in React

Testing the useLocation hook from React Router is essential for ensuring your components correctly respond to URL and state changes. This article provides a straightforward guide on how to test components utilizing useLocation using Jest and React Testing Library. We will cover the two best approaches: wrapping your component with MemoryRouter for realistic integration testing, and mocking the hook directly for isolated unit tests.

The most reliable way to test useLocation is by wrapping your component in a MemoryRouter. This provider from react-router-dom allows you to simulate different URL paths and location states without needing a real browser environment.

The Component under Test

Consider this component that displays the current pathname and a custom state value passed through the navigation:

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

export default function LocationDisplay() {
  const location = useLocation();
  return (
    <div>
      <p data-testid="pathname">{location.pathname}</p>
      <p data-testid="state-value">{location.state?.from || 'No State'}</p>
    </div>
  );
}

The Test Case

Using MemoryRouter, you can define initialEntries to mock the history stack, including the pathname and location state:

import React from 'react';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import LocationDisplay from './LocationDisplay';

test('renders pathname and state from useLocation', () => {
  render(
    <MemoryRouter initialEntries={[{ pathname: '/dashboard', state: { from: 'login' } }]}>
      <LocationDisplay />
    </MemoryRouter>
  );

  expect(screen.getByTestId('pathname')).toHaveTextContent('/dashboard');
  expect(screen.getByTestId('state-value')).toHaveTextContent('login');
});

Method 2: Mocking useLocation with Jest

If you want to isolate your component completely and avoid importing MemoryRouter, you can mock the useLocation hook directly using Jest.

The Test Case

We use jest.mock to intercept react-router-dom and define what useLocation should return:

import React from 'react';
import { render, screen } from '@testing-library/react';
import LocationDisplay from './LocationDisplay';

// Mock react-router-dom
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useLocation: () => ({
    pathname: '/profile',
    state: { from: 'homepage' }
  })
}));

test('renders mocked pathname and state', () => {
  render(<LocationDisplay />);

  expect(screen.getByTestId('pathname')).toHaveTextContent('/profile');
  expect(screen.getByTestId('state-value')).toHaveTextContent('homepage');
});

Using MemoryRouter is generally preferred as it closely mirrors real-world application behavior and avoids fragile mock setups. However, mock overrides are useful when you need to test multiple complex routing states in a single test file.