NeuroAgent

Fixing MockNavigate Issues in React Router Tests

Learn why your mockNavigate function isn't being called in React Router tests and discover proven solutions to properly mock the useNavigate hook for reliable component testing.

Question

Why is my mockNavigate function not being called in my React Router tests?

I’m trying to test a component that uses the useNavigate hook from React Router. Here’s my test setup:

javascript
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: jest.fn()
}));

describe('MyTest wrapper', () => {
  const mockNavigate = jest.fn();

  beforeEach(() => {
    (useNavigate as jest.Mock).mockReturnValue(mockNavigate);
  });

  it('should navigate to correct path', () => {
    // ... test setup
    await waitFor(() =>
      expect(mockNavigate).toHaveBeenCalledWith(RouterHelpers.somePath())
    );
  });
});

The problem is that my mockNavigate function is never called during the test. What could be causing this issue, and how can I properly mock the useNavigate hook for testing?

NeuroAgent

Your mockNavigate function isn’t being called because the mock setup isn’t properly intercepting the useNavigate hook calls in your test environment. This is a common issue when testing React Router components, often caused by incorrect mock placement, timing issues, or not properly wrapping components with necessary routing context.

Contents

Common Causes of Mock Issues

The mockNavigate function not being called typically stems from one or more of these issues:

  1. Incorrect mock placement: The mock isn’t set up at the module level before the component loads
  2. Timing problems: The mock is set up after the component has already rendered
  3. Context missing: Component tests need proper routing context (MemoryRouter)
  4. Hook resolution: The mock isn’t resolving properly when useNavigate is called

Solution 1: Top-Level Mock Setup

The most reliable approach is to set up the mock at the module level, before any imports. This ensures the mock is in place before your component tries to use useNavigate:

javascript
// At the top level of your test file
const mockNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockNavigate,
}));

describe('MyTest wrapper', () => {
  beforeEach(() => {
    mockNavigate.mockClear();
  });

  it('should navigate to correct path', () => {
    // Your test setup here
    expect(mockNavigate).toHaveBeenCalledWith(RouterHelpers.somePath());
  });
});

Solution 2: beforeEach with jest.spyOn

For more complex scenarios, you can use jest.spyOn in beforeEach:

javascript
import * as router from 'react-router-dom';

describe('MyTest wrapper', () => {
  const mockNavigate = jest.fn();

  beforeEach(() => {
    jest.spyOn(router, 'useNavigate').mockImplementation(() => mockNavigate);
    mockNavigate.mockClear();
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('should navigate to correct path', () => {
    // Test code here
    expect(mockNavigate).toHaveBeenCalledWith(RouterHelpers.somePath());
  });
});

Solution 3: Component Testing with MemoryRouter

When testing components that use useNavigate, you need to provide proper routing context:

javascript
import { MemoryRouter } from 'react-router-dom';

test('should navigate to correct path', () => {
  const mockNavigate = jest.fn();
  
  jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useNavigate: () => mockNavigate,
  }));

  render(
    <MemoryRouter initialEntries={['/']}>
      <YourComponent />
    </MemoryRouter>
  );

  // Trigger navigation
  userEvent.click(screen.getByText('Navigate Button'));

  expect(mockNavigate).toHaveBeenCalledWith('/target-path');
});

Solution 4: Custom Hook Testing

For custom hooks that use useNavigate, the approach is slightly different:

javascript
import { renderHook } from '@testing-library/react-hooks';

const mockNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockNavigate,
}));

describe('useCustomNavigation', () => {
  beforeEach(() => {
    mockNavigate.mockClear();
  });

  it('should navigate when called', () => {
    const { result } = renderHook(() => useCustomNavigation());
    
    result.current.navigateToTarget();
    
    expect(mockNavigate).toHaveBeenCalledWith('/target');
  });
});

Best Practices

  1. Mock at module level: Always set up mocks before importing components
  2. Clear mocks between tests: Use mockClear() in beforeEach
  3. Use proper context: Wrap components in MemoryRouter when needed
  4. Verify mock calls: Use expect(mockNavigate).toHaveBeenCalled() and toHaveBeenCalledWith()
  5. Handle async navigation: Use act() and waitFor() for async navigation calls

Complete Working Example

Here’s a complete working example for testing a component with useNavigate:

javascript
// MyComponent.test.js
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
import MyComponent from './MyComponent';

// Mock at module level
const mockNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockNavigate,
}));

describe('MyComponent', () => {
  beforeEach(() => {
    mockNavigate.mockClear();
  });

  it('should navigate when button is clicked', async () => {
    render(
      <MemoryRouter>
        <MyComponent />
      </MemoryRouter>
    );

    const button = screen.getByRole('button', { name: /go to profile/i });
    await userEvent.click(button);

    expect(mockNavigate).toHaveBeenCalledWith('/profile');
  });

  it('should navigate with state when required', async () => {
    render(
      <MemoryRouter>
        <MyComponent />
      </MemoryRouter>
    );

    const button = screen.getByRole('button', { name: /go with state/i });
    await userEvent.click(button);

    expect(mockNavigate).toHaveBeenCalledWith('/dashboard', {
      state: { from: '/home' }
    });
  });
});

The key is ensuring your mock is set up correctly before your component tries to use useNavigate, and providing the proper routing context when testing components directly.

Sources

  1. React Router useNavigate hook mocking with Jest - Stack Overflow
  2. Testing useNavigate() / navigate() from react-router v6 – Paweł Gościcki
  3. How to mock React-Router-Dom hooks in Jest
  4. Testing useNavigate - DEV Community
  5. Mocking useNavigate with jest does not work - GitHub Issue

Conclusion

The main reasons your mockNavigate function isn’t being called are typically related to mock setup timing, missing routing context, or incorrect mock implementation. By following the solutions above - particularly setting up mocks at the module level and providing proper routing context with MemoryRouter - you should be able to successfully test your React Router navigation functionality.

Remember to always clear your mocks between tests and verify both that the function was called and with the expected arguments. If you’re still having issues, double-check that your component is actually calling useNavigate() and that there are no conditional statements preventing the navigation call.