特性
- 直接在你的故事中模拟 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,
})
}),
],
},
},
}
然后,你可以在你的单个故事中使用其他处理程序。Storybook 将合并全局处理程序和故事处理程序。
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 的全局处理程序。你只需在故事中再次设置它们,这些值将具有优先权。
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,它们将在你的故事中被忽略。
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',
})
如果你想在故事发出本应处理但未处理的请求时发出有用的警告消息
// .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 的合并逻辑,该逻辑 仅在故事参数中的处理程序是对象而不是数组时才有效。为了解决此限制,你可以将初始请求处理程序作为第二个参数直接传递给 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 中使用可移植故事配合插件
如果你正在使用 可移植故事,你需要确保 MSW loader 已正确应用。
Storybook 8.2 或更高版本
如果你 正确设置了项目注解,通过调用故事的 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
你可以通过在渲染故事之前调用 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
工具应该是一个内部细节,由可移植故事自动调用,但由于在 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