How to Test useSearchParams Hook in React
Testing the useSearchParams hook from
react-router-dom is essential for verifying that your React
components correctly read and update URL query parameters. This article
provides a straightforward guide on how to test components using this
hook, demonstrating both the integration approach using
MemoryRouter and the unit testing approach using Jest
mocks.
Testing with MemoryRouter (Recommended)
The most robust way to test useSearchParams is to wrap
your component under test with MemoryRouter from
react-router-dom. This provides a realistic routing
context, allowing you to set initial query parameters and assert how
your component updates them without mocking React Router internals.
Here is a simple component that uses
useSearchParams:
import { useSearchParams } from 'react-router-dom';
export function SearchComponent() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('query') || '';
return (
<div>
<p>Current Query: {query}</p>
<button onClick={() => setSearchParams({ query: 'react' })}>
Search React
</button>
</div>
);
}To test this component using React Testing Library and
@testing-library/user-event, you can use
MemoryRouter and pass the initial query parameters via the
initialEntries prop:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import { SearchComponent } from './SearchComponent';
describe('SearchComponent with MemoryRouter', () => {
test('reads initial search params and updates them on click', async () => {
render(
<MemoryRouter initialEntries={['/?query=javascript']}>
<SearchComponent />
</MemoryRouter>
);
// Verify initial state from URL
expect(screen.getByText('Current Query: javascript')).toBeInTheDocument();
// Simulate clicking the button to update query parameters
const button = screen.getByRole('button', { name: /search react/i });
await userEvent.click(button);
// Verify the UI updates with the new search param value
expect(screen.getByText('Current Query: react')).toBeInTheDocument();
});
});Testing by Mocking useSearchParams
If you prefer to isolate your component completely from
react-router-dom behavior, you can mock the
useSearchParams hook using Jest. This approach is useful
when you want to assert that the setter function is called with specific
arguments without relying on the router’s context.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SearchComponent } from './SearchComponent';
import * as router from 'react-router-dom';
// Mock the react-router-dom library
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useSearchParams: jest.fn(),
}));
describe('SearchComponent with Mocking', () => {
const mockSetSearchParams = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
test('calls setSearchParams with correct arguments', async () => {
// Setup the mock return value for useSearchParams
const mockSearchParams = new URLSearchParams('query=javascript');
jest.spyOn(router, 'useSearchParams').mockReturnValue([
mockSearchParams,
mockSetSearchParams,
]);
render(<SearchComponent />);
// Verify initial render uses mock search params
expect(screen.getByText('Current Query: javascript')).toBeInTheDocument();
// Trigger update
const button = screen.getByRole('button', { name: /search react/i });
await userEvent.click(button);
// Assert that the setter function was called correctly
expect(mockSetSearchParams).toHaveBeenCalledWith({ query: 'react' });
});
});