How to Test SWR in React

Testing components that use the SWR (Stale-While-Revalidate) library in React requires handling asynchronous data fetching and cache management. This guide provides a straightforward approach to testing SWR-powered components using React Testing Library and Mock Service Worker (MSW). You will learn how to set up your test environment, clear the SWR cache between tests to prevent test pollution, and write robust assertions for loading, success, and error states.

Setting Up the Test Environment

To write reliable tests for SWR, you need to mock the network requests. Using Mock Service Worker (MSW) is the recommended approach because it intercepts requests at the network level, allowing your SWR fetcher to execute naturally.

First, install the necessary dependencies:

npm install -D @testing-library/react @testing-library/jest-dom msw

Next, configure your MSW server to mock the API endpoints your SWR hooks call:

// src/mocks/server.js
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/user', () => {
    return HttpResponse.json({ name: 'John Doe' });
  }),
];

export const server = setupServer(...handlers);

Set up your Jest configuration to start and clean up the MSW server during the test lifecycle:

// src/setupTests.js
import '@testing-library/jest-dom';
import { server } from './mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Resetting SWR Cache Between Tests

SWR caches fetch results globally. If you do not clear the cache between tests, state from one test will leak into the next, causing false positives or unexpected failures.

To prevent this, wrap your test components in an <SWRConfig> provider with an empty cache provider (provider: () => new Map()) and set dedupingInterval: 0.

Create a custom render helper or a test wrapper like this:

import React from 'react';
import { render } from '@testing-library/react';
import { SWRConfig } from 'swr';

const AllTheProviders = ({ children }) => {
  return (
    <SWRConfig value={{ provider: () => new Map(), dedupingInterval: 0 }}>
      {children}
    </SWRConfig>
  );
};

const customRender = (ui, options) =>
  render(ui, { wrapper: AllTheProviders, ...options });

export * from '@testing-library/react';
export { customRender as render };

Writing the Tests

Consider a simple React component that fetches user data using SWR:

// UserProfile.jsx
import React from 'react';
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

export function UserProfile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Failed to load user.</div>;
  return <h1>Hello, {data.name}</h1>;
}

Here is how you test the loading, success, and error states of this component:

// UserProfile.test.jsx
import React from 'react';
import { render, screen, waitFor } from './custom-render'; // Import your custom render
import { server } from './mocks/server';
import { http, HttpResponse } from 'msw';
import { UserProfile } from './UserProfile';

describe('UserProfile Component', () => {
  test('renders loading state initially', () => {
    render(<UserProfile />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });

  test('renders user data on successful fetch', async () => {
    render(<UserProfile />);

    // Wait for the loader to disappear and the user data to render
    const heading = await screen.findByRole('heading', { level: 1 });
    expect(heading).toHaveTextContent('Hello, John Doe');
  });

  test('renders error state on API failure', async () => {
    // Override MSW handler to return a 500 error for this specific test
    server.use(
      http.get('/api/user', () => {
        return new HttpResponse(null, { status: 500 });
      })
    );

    render(<UserProfile />);

    await waitFor(() => {
      expect(screen.getByText('Failed to load user.')).toBeInTheDocument();
    });
  });
});

Using this setup ensures your SWR tests run in complete isolation, execute actual fetch workflows, and accurately reflect user-facing states.