加入直播会话:周四,美国东部时间上午 11 点,Storybook 9 版本发布 & AMA

Mock Service Worker

使用 Mock Service Worker 在 Storybook 中模拟 API 请求。

在 Github 上查看

特性

  • 在您的 story 中直接模拟 Rest 和 GraphQL 请求。
  • 记录组件在各种场景下的行为方式。
  • 通过其他插件免费获得 a11y、快照和可视化测试。

完整的文档和实时演示

安装和设置

安装 MSW 和插件

使用 npm

npm i msw msw-storybook-addon -D

或使用 yarn

yarn add msw msw-storybook-addon -D

在您的 public 文件夹中为 MSW 生成 service worker。

如果您的项目中已经使用了 MSW,您很可能之前已经做过这一步,因此可以跳过。

npx msw init public/

如果您不使用 public,请参阅 MSW 官方指南以获取框架特定的路径。

配置插件

通过初始化 MSW 并在 ./storybook/preview.js 中提供 MSW loader,在 Storybook 中启用 MSW。

import { initialize, mswLoader } from 'msw-storybook-addon'

// Initialize MSW
initialize()

const preview = {
  parameters: {
    // your other code...
  },
  // Provide the MSW addon loader globally
  loaders: [mswLoader],
}

export default preview

启动 Storybook

运行 Storybook 时,您必须将 public 文件夹作为资源提供给 Storybook,以便包含 MSW,否则它将在浏览器中不可用。

这意味着您应该在 Storybook 主配置文件中设置 staticDirs 字段。如有需要,请参阅文档

npm run storybook

用法

您可以将请求处理器(https://mswjs.io/docs/concepts/request-handler)传递到 msw 参数的 handlers 属性中。这通常是一个处理器数组。

import { http, HttpResponse } from 'msw'

export const SuccessBehavior = {
  parameters: {
    msw: {
      handlers: [
        http.get('/user', () => {
          return HttpResponse.json({
            firstName: 'Neil',
            lastName: 'Maverick',
          })
        }),
      ],
    },
  },
}

高级用法

组合请求处理器

handlers 属性也可以是一个对象,其中键是处理器数组或处理器本身。这使您可以使用参数继承从 preview.js 继承(并可选择覆盖/禁用)处理器。

type MswParameter = {
  handlers: RequestHandler[] | Record<string, RequestHandler | RequestHandler[]>
}

假设您有一个应用程序,其中几乎每个组件都需要以相同的方式模拟对 /login/logout 的请求。您可以在 preview.js 中为这些请求设置全局 MSW 处理器,并将它们捆绑到一个名为 auth 的属性中,例如

//preview.ts
import { http, HttpResponse } from 'msw'

// These handlers will be applied in every story
export const parameters = {
  msw: {
    handlers: {
      auth: [
        http.get('/login', () => {
          return HttpResponse.json({
            success: true,
          })
        }),
        http.get('/logout', () => {
          return HttpResponse.json({
            success: true,
          })
        }),
      ],
    },
  },
}

然后,您可以在您的单个 story 中使用其他处理器。Storybook 将合并全局处理器和 story 处理器

import { http, HttpResponse } from 'msw'

// This story will include the auth handlers from .storybook/preview.ts and profile handlers
export const SuccessBehavior = {
  parameters: {
    msw: {
      handlers: {
        profile: http.get('/profile', () => {
          return HttpResponse.json({
            firstName: 'Neil',
            lastName: 'Maverick',
          })
        }),
      },
    },
  },
}

现在假设您想覆盖 auth 的全局处理器。您只需在您的 story 中再次设置它们,这些值将优先。

import { http, HttpResponse } from 'msw'

// This story will overwrite the auth handlers from preview.ts
export const FailureBehavior = {
  parameters: {
    msw: {
      handlers: {
        auth: http.get('/login', () => {
          return HttpResponse.json(null, { status: 403 })
        }),
      },
    },
  },
}

如果您想禁用全局处理器怎么办?您只需将它们设置为 null,它们将在您的 story 中被忽略。

import { http, HttpResponse } from 'msw'

// This story will disable the auth handlers from preview.ts
export const NoAuthBehavior = {
  parameters: {
    msw: {
      handlers: {
        auth: null,
        others: [
          http.get('/numbers', () => {
            return HttpResponse.json([1, 2, 3])
          }),
          http.get('/strings', () => {
            return HttpResponse.json(['a', 'b', 'c'])
          }),
        ],
      },
    },
  },
}

配置 MSW

msw-storybook-addon 使用默认配置启动 MSW。initialize 接受两个参数

  • options:这会在浏览器中时传递给 worker.start(),在 Node 中时传递给 server.listen(),因此预期类型相同。
  • initialHandlers:一个 RequestHandler[] 类型,此数组会在浏览器中时展开到 setupWorker(),在 Node 中时展开到 setupServer()

一个常见的例子是配置 onUnhandledRequest 行为,因为在存在未处理的请求时,MSW 会记录警告。

如果您想让 MSW 绕过未处理的请求,不做任何处理

// .storybook/preview.ts
import { initialize } from 'msw-storybook-addon'

initialize({
  onUnhandledRequest: 'bypass',
})

如果您想在 stories 发出本应被处理但未被处理的请求时发出友好的警告消息

// .storybook/preview.ts
import { initialize } from 'msw-storybook-addon'

initialize({
  onUnhandledRequest: ({ url, method }) => {
    const pathname = new URL(url).pathname
    if (pathname.startsWith('/my-specific-api-path')) {
      console.error(`Unhandled ${method} request to ${url}.

        This exception has been only logged in the console, however, it's strongly recommended to resolve this error as you don't want unmocked data in Storybook stories.

        If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses
      `)
    }
  },
})

尽管可以组合处理器,但这依赖于 Storybook 的合并逻辑,该逻辑仅在您的 story 参数中的处理器是对象而不是数组时才有效。要解决此限制,您可以将初始请求处理器直接作为第二个参数传递给 initialize 函数。

// .storybook/preview.ts
import { http, HttpResponse } from 'msw'
import { initialize } from 'msw-storybook-addon'

initialize({}, [
  http.get('/numbers', () => {
    return HttpResponse.json([1, 2, 3])
  }),
  http.get('/strings', () => {
    return HttpResponse.json(['a', 'b', 'c'])
  }),
])

在 Node.js 中使用 Portable Stories 使用插件

如果您正在使用portable stories,您需要确保 MSW loader 已正确应用。

Storybook 8.2 或更高版本

如果您正确地设置项目注解,通过调用您的 story 的 play 函数,MSW loader 将自动应用。

import { composeStories } from '@storybook/react'
import * as stories from './MyComponent.stories'

const { Success } = composeStories(stories)

test('<Success />', async() => {
  // The MSW loaders are applied automatically via the play function
  await Success.play()
})

Storybook < 8.2

您可以通过在渲染您的 story 之前调用 applyRequestHandlers 助手函数来实现。

import { applyRequestHandlers } from 'msw-storybook-addon'
import { composeStories } from '@storybook/react'
import * as stories from './MyComponent.stories'

const { Success } = composeStories(stories)

test('<Success />', async() => {
  // 👇 Crucial step, so that the MSW loaders are applied
  await applyRequestHandlers(Success.parameters.msw)
  render(<Success />)
})

注意:applyRequestHandlers 工具应该是一个内部细节,由 portable stories 自动调用,但由于在 Storybook 7 中不可能实现,因此由插件导出。它将在未来的版本中移除,因此建议您在可能的情况下升级到 Storybook 8。

故障排除

MSW 干扰了 HMR(热模块替换)

如果您在控制台中遇到诸如 [MSW] Failed to mock a "GET" request to "http://localhost:6006/4cb31fa2eee22cf5b32f.hot-update.json" 的问题,很可能是 MSW 干扰了 HMR。这种情况不常见,似乎只发生在 Webpack 项目中,但如果发生了,您可以按照此 issue 中的步骤进行修复

https://github.com/mswjs/msw-storybook-addon/issues/36#issuecomment-1496150729