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:
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?
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
- Solution 1: Top-Level Mock Setup
- Solution 2: beforeEach with jest.spyOn
- Solution 3: Component Testing with MemoryRouter
- Solution 4: Custom Hook Testing
- Best Practices
- Complete Working Example
Common Causes of Mock Issues
The mockNavigate function not being called typically stems from one or more of these issues:
- Incorrect mock placement: The mock isn’t set up at the module level before the component loads
- Timing problems: The mock is set up after the component has already rendered
- Context missing: Component tests need proper routing context (MemoryRouter)
- Hook resolution: The mock isn’t resolving properly when
useNavigateis 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:
// 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:
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:
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:
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
- Mock at module level: Always set up mocks before importing components
- Clear mocks between tests: Use
mockClear()inbeforeEach - Use proper context: Wrap components in
MemoryRouterwhen needed - Verify mock calls: Use
expect(mockNavigate).toHaveBeenCalled()andtoHaveBeenCalledWith() - Handle async navigation: Use
act()andwaitFor()for async navigation calls
Complete Working Example
Here’s a complete working example for testing a component with useNavigate:
// 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
- React Router useNavigate hook mocking with Jest - Stack Overflow
- Testing useNavigate() / navigate() from react-router v6 – Paweł Gościcki
- How to mock React-Router-Dom hooks in Jest
- Testing useNavigate - DEV Community
- 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.