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();
});