MSW Storybook Addon

2.0.7 · active · verified Wed Apr 22

The `msw-storybook-addon` package provides a seamless integration between Storybook and Mock Service Worker (MSW), allowing developers to effectively mock API requests directly within their Storybook stories. This integration is crucial for isolated component development, enabling consistent testing environments, and showcasing various data states without requiring a live backend or complex setup. The current stable version is 2.0.7, with the project maintaining an active release cadence, frequently publishing bug fixes and minor enhancements. Its key differentiator lies in its ability to leverage MSW's powerful network interception capabilities, applying mock handlers globally across all stories or specifically overriding them on a per-story basis. This flexibility is achieved using Storybook's parameters and loaders system, facilitating robust mocking for REST, GraphQL, and other network protocols in both browser and Node.js environments. It simplifies the creation of reproducible UI states dependent on API responses.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to set up `msw-storybook-addon` and define MSW handlers globally for a component's stories, as well as how to override handlers on a per-story basis. It also shows a basic Storybook `play` function to verify the mocked data.

import type { Meta, StoryObj } from '@storybook/react';
import { http, HttpResponse } from 'msw';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { within, expect } from '@storybook/test';

// Assuming a simple React component that fetches user data
const UserProfile = () => {
  const [user, setUser] = React.useState(null);
  React.useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);

  if (!user) return <div>Loading user...</div>;
  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
    </div>
  );
};

// --- Configure in .storybook/preview.ts (or .js) ---
// initialize({
//   onUnhandledRequest: 'bypass',
// });
// export const loaders = [mswLoader];
// ---------------------------------------------------

const meta: Meta<typeof UserProfile> = {
  title: 'Components/UserProfile',
  component: UserProfile,
  parameters: {
    // Default MSW handlers for all stories under this meta
    msw: {
      handlers: [
        http.get('/api/user', () => {
          return HttpResponse.json({ name: 'John Doe', email: 'john.doe@example.com' });
        }),
      ],
    },
  },
  // Apply mswLoader globally to ensure mocks are active
  loaders: [mswLoader]
};

export default meta;

type Story = StoryObj<typeof UserProfile>;

export const DefaultUser: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await canvas.findByText('Name: John Doe');
    await expect(canvas.findByText('Email: john.doe@example.com')).toBeInTheDocument();
  },
};

export const AdminUser: Story = {
  parameters: {
    msw: {
      handlers: [
        // Override specific handlers for this story
        http.get('/api/user', () => {
          return HttpResponse.json({ name: 'Admin User', email: 'admin@example.com', role: 'admin' });
        }),
      ],
    },
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await canvas.findByText('Name: Admin User');
    await expect(canvas.findByText('Email: admin@example.com')).toBeInTheDocument();
  },
};

view raw JSON →