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.

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