How to Test NavLink Component in React

Testing the NavLink component from React Router is essential for ensuring your application’s navigation paths work correctly and apply active styling states when selected. This guide demonstrates how to test the NavLink component using Jest and React Testing Library. You will learn how to wrap the component in a mock routing context, verify its rendering attributes, test the active state application, and simulate user navigation events.

Why Wrapping is Required

The NavLink component relies on React Router’s context to detect the current URL and navigate. If you attempt to render it in isolation within a test, your test suite will throw an error. To prevent this, you must wrap the component under test inside a router utility, such as MemoryRouter. This component stores its location internally in memory and allows you to set initial routes for testing purposes.

Basic Rendering Test

The first step in testing a NavLink is ensuring it renders onto the screen with the correct destination URL in its href attribute.

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

test('renders NavLink with the correct path destination', () => {
  render(
    <MemoryRouter>
      <NavLink to="/profile">Profile</NavLink>
    </MemoryRouter>
  );

  const linkElement = screen.getByRole('link', { name: /profile/i });
  expect(linkElement).toBeInTheDocument();
  expect(linkElement).toHaveAttribute('href', '/profile');
});

Testing the Active State

The distinguishing feature of a NavLink is its ability to apply specific styling or a custom class when the current URL matches the link’s target path. You can verify this behavior by configuring the initialEntries prop on the MemoryRouter to match the path of your link.

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

test('applies active class when the route matches the link path', () => {
  render(
    <MemoryRouter initialEntries={['/settings']}>
      <NavLink 
        to="/settings" 
        className={({ isActive }) => isActive ? 'nav-active' : 'nav-inactive'}
      >
        Settings
      </NavLink>
    </MemoryRouter>
  );

  const linkElement = screen.getByRole('link', { name: /settings/i });
  expect(linkElement).toHaveClass('nav-active');
  expect(linkElement).not.toHaveClass('nav-inactive');
});

Testing User Navigation and State Changes

To test real user behavior, you can simulate a mouse click on the NavLink using the @testing-library/user-event library. This test structure configures multiple paths to ensure that clicking the inactive link successfully updates the DOM with the active CSS class and renders the matching route component.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, NavLink, Routes, Route } from 'react-router-dom';

test('updates active status and UI on user click', async () => {
  render(
    <MemoryRouter initialEntries={['/home']}>
      <nav>
        <NavLink to="/home" className={({ isActive }) => isActive ? 'active' : ''}>Home</NavLink>
        <NavLink to="/dashboard" className={({ isActive }) => isActive ? 'active' : ''}>Dashboard</NavLink>
      </nav>

      <Routes>
        <Route path="/home" element={<div>Home View</div>} />
        <Route path="/dashboard" element={<div>Dashboard View</div>} />
      </Routes>
    </MemoryRouter>
  );

  const homeLink = screen.getByRole('link', { name: /home/i });
  const dashboardLink = screen.getByRole('link', { name: /dashboard/i });

  // Verify initial state
  expect(homeLink).toHaveClass('active');
  expect(dashboardLink).not.toHaveClass('active');
  expect(screen.getByText('Home View')).toBeInTheDocument();

  // Simulate user clicking on the Dashboard link
  await userEvent.click(dashboardLink);

  // Verify state updates after interaction
  expect(dashboardLink).toHaveClass('active');
  expect(homeLink).not.toHaveClass('active');
  expect(screen.getByText('Dashboard View')).toBeInTheDocument();
});