msw-storybook-addon

repository·main·Indexed 19 days ago

https://github.com/mswjs/msw-storybook-addon

A Storybook addon for mocking API requests using Mock Service Worker (MSW) to enable isolated and predictable component testing. Supports CSF 3.0 via mswLoader and CSF Next via addonMsw, providing mechanisms for global and story-specific handlers through the beforeEach hook. Version 3.0.0 requires msw@2.x as a peer dependency.

Tokens
4.5K
Snippets
20
Records
22
Agent score
55%

What's inside msw-storybook-addon

  1. Automate migration from 2.x.x to 3.x.x

    main

    Most migration tasks when upgrading from version 2.x.x to 3.x.x can be automated. Run the following command to rewrite your preview and main configurations and migrate parameters.msw to beforeEach in your stories. Any manual migrations required will be listed in the output.

    npx msw-storybook-addon-migrate
  2. Customize the MSW worker setup

    main

    By default, the addon manages the worker lifecycle. To customize behavior (like worker.start() options or initial handlers), provide a setup function that creates, starts, and returns the worker.

    Important: Handlers passed to setupWorker() act as initial handlers and are not reset between stories.

    • In CSF 3.0: Pass the setup function to mswLoader.
    • In CSF Next: Pass the setup function to addonMsw.
    // CSF 3.0 Custom Setup
    import { setupWorker } from 'msw/browser'
    import { mswLoader } from 'msw-storybook-addon/csf3'
    
    export default {
      loaders: [
        mswLoader(async () => {
          const worker = setupWorker()
          await worker.start({ onUnhandledRequest: 'bypass' })
          return worker
        })
      ]
    }
    // CSF Next Custom Setup
    import { setupWorker } from 'msw/browser'
    import addonMsw from 'msw-storybook-addon'
    
    export default definePreview({
      addons: [
        addonMsw(async () => {
          const worker = setupWorker()
          await worker.start({ onUnhandledRequest: 'bypass' })
          return worker
        })
      ],
    })
  3. Handle Node.js environments in v3.x.x

    main

    Version 3.x.x drops native Node.js support, as the addon defaults to running MSW in the browser. If you render stories in Node.js (e.g., for server-side testing) and require msw/node, you must implement a custom setup function that detects the environment and returns either a setupWorker or setupServer instance. Because the setup function is typed to return a browser worker, you may need to cast the server instance to unknown as SetupWorker.

    // .storybook/preview.ts
    import type { SetupWorker } from 'msw/browser'
    import { mswLoader } from 'msw-storybook-addon/csf3'
    
    const preview = {
      loaders: [
        mswLoader(async () => {
          if (typeof document === 'undefined') {
            const { setupServer } = await import('msw/node')
            const server = setupServer()
            server.listen()
            return server as unknown as SetupWorker
          }
    
          const { setupWorker } = await import('msw/browser')
          const worker = setupWorker()
          await worker.start()
          return worker
        })
      ]
    }
    
    export default preview
  4. Replace initialize with a custom setup function in v3.x.x

    main

    In version 3.x.x, the initialize function has been removed. The addon now manages the worker lifecycle automatically. If you previously used initialize to pass custom options (like onUnhandledRequest) or initial handlers, you must now pass a custom setup function to mswLoader. This setup function is responsible for creating and starting the worker.

    // .storybook/preview.js
    import { setupWorker } from 'msw/browser'
    import { mswLoader } from 'msw-storybook-addon/csf3'
    
    const preview = {
      loaders: [
        mswLoader(async () => {
          const worker = setupWorker()
          await worker.start({ onUnhandledRequest: 'bypass' })
          return worker
        })
      ]
    }
    
    export default preview
  5. Migrate from parameters.msw to beforeEach in v3.x.x

    main

    In version 3.x.x, parameters.msw is deprecated in favor of using the beforeEach hook. The addon now provides an msw property within the story context. Using beforeEach is the recommended way to add request handlers globally in preview.ts or on a per-story basis. Handlers are automatically reset between stories.

    // ❌ Deprecated: defining handlers in parameters
    export const MyStory = {
      parameters: {
        msw: {
          handlers: [...] 
        }
      }
    }
    
    // ✅ Recommended: using the beforeEach hook
    export const MyStory = {
      beforeEach({ msw }) {
        msw.use(...)
      }
    }
  6. Configure Storybook using CSF Next

    main

    For CSF Next (CSF Factories), simply import and call the addon function within definePreview in your preview.ts file.

    Note: parameters.msw is not supported in CSF Next. Instead, use the beforeEach hook to manage handlers.

    To enable type safety, add msw-storybook-addon/types to the types array in your tsconfig.json.

    // .storybook/preview.ts
    import addonMsw from 'msw-storybook-addon'
    
    export default definePreview({
      addons: [
        addonMsw(),
      ],
    })
    {
      "include": [".storybook/preview.ts", "..."],
      "compilerOptions": {
        "types": ["msw-storybook-addon/types"]
      }
    }
  7. Provide MSW handlers in stories

    main

    The addon extends the story context with an msw property. You can use msw.use() to control API mocking.

    Global Handlers

    Define network behaviors for all stories in .storybook/preview.ts using the beforeEach hook.

    Story-specific Handlers

    Define specific network behaviors for a single story by adding a beforeEach hook directly to that story object.

    // Global handlers in .storybook/preview.ts
    import { http, HttpResponse } from 'msw'
    
    export default {
      beforeEach({ msw }) {
        msw.use(
          http.get('https://api.acme.com/user', () => {
            return HttpResponse.json({ name: 'John Maverick' })
          }),
        )
      },
    }
    // Story-specific handlers
    export const UserProfileNetworkError: Story = {
      beforeEach({ msw }) {
        msw.use(
          http.get('https://api.acme.com/user', () => {
            return HttpResponse.error()
          }),
        )
      },
    }
  8. Configure Storybook using CSF 3.0

    main

    Note: The CSF 3.0 loader API is deprecated. Use the CSF Next API instead if possible.

    To use CSF 3.0:

    1. Add the addon to .storybook/main.ts.
    2. Import and use mswLoader in .storybook/preview.ts.
    3. Define initial handlers in parameters.msw.

    To enable type safety for parameters.msw, add msw-storybook-addon/csf3 to the types array in your tsconfig.json.

    // .storybook/main.ts
    export default {
      addons: ['msw-storybook-addon'],
    }
    // .storybook/preview.ts
    import { mswLoader } from 'msw-storybook-addon/csf3'
    
    export default {
      loaders: [mswLoader()],
      parameters: {
        msw: [...initialHandlers]
      }
    }
    {
      "include": [".storybook/preview.ts", "..."],
      "compilerOptions": {
        "types": ["msw-storybook-addon/csf3"]
      }
    }
  9. Use CSF Next (CSF Factories) with msw-storybook-addon

    main

    If you are using CSF Next syntax, you do not need to use mswLoader. Instead, import and call addonMsw directly within the addons array of your definePreview configuration in preview.ts. Note that parameters.msw is not supported in CSF Next; you must use the beforeEach hook to manage handlers.

    // .storybook/preview.ts
    import addonMsw from 'msw-storybook-addon'
    
    export default definePreview({
      addons: [addonMsw()],
    })
  10. Migrate from mswDecorator to mswLoader (v1.x.x to v2.x.x)

    main

    To prevent race conditions where stories request data before the service worker is registered, replace mswDecorator with mswLoader. Loaders execute before a story renders, whereas decorators execute during the render process. mswLoader respects the same parameters.msw settings.

    // .storybook/preview.js
    -import { initialize, mswDecorator } from 'msw-storybook-addon'
    +import { initialize, mswLoader } from 'msw-storybook-addon'
    
    initialize()
    
    const preview = {
    -  decorators: [mswDecorator]
    +  loaders: [mswLoader]
    }
    
    export default preview