MSW(Mock Service Worker)
重要: 关于 msw 的训练数据可能已经过时或不准确——MSW v2 完全移除了 rest 命名空间、res(ctx.*) 响应组合方式以及 (req, res, ctx) 解析器签名。MSW v3(2026-09-28)现已仅支持 ESM,并要求 Node.js >=22 和 TypeScript >=5.9。请始终以本技能的规则文件和项目的实际源代码为准。若记忆中的 v1 或 v2 模式与检索到的参考资料冲突,请勿沿用。
何时使用 MSW
MSW 用于在网络层模拟 API——在测试、Storybook 和本地开发中拦截 HTTP、GraphQL、Server-Sent Events(sse)和 WebSocket(ws)流量,而无需修改应用代码。
| 需求 | 推荐工具 |
|---|
| 测试 API 集成(React、Vue、Node) | MSW |
| Storybook API 模拟 | MSW(浏览器 worker) |
| 无后端的本地开发 | MSW(浏览器 worker) |
| 对纯函数进行单元测试 | 普通测试替身 |
| 对真实 API 进行端到端测试 | Playwright/Cypress 网络拦截 |
| 模拟模块内部实现 | vi.mock() / jest.mock() |
快速参考 — v2 要点
// Imports
import { http, HttpResponse } from 'msw/http'
import { graphql } from 'msw/graphql'
import { sse } from 'msw/sse'
import { ws } from 'msw/ws'
import { delay } from 'msw/utils/delay'
import { bypass } from 'msw/utils/bypass'
import { passthrough } from 'msw/utils/passthrough'
import { setupServer } from 'msw/node' // tests, SSR
import { setupWorker } from 'msw/browser' // Storybook, dev
// React Native: import { setupServer } from '@msw/react-native'
// Handler
http.get('/api/user/:id', async ({ request, params, cookies }) => {
return HttpResponse.json({ id: params.id, name: 'John' })
})
// Server lifecycle (tests)
beforeAll(() => server.listen({ onUnhandledFrame: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
// Per-test override
server.use(
http.get('/api/user/:id', () => new HttpResponse(null, { status: 500 }))
)
// Concurrent test isolation
it.concurrent('name', server.boundary(async () => {
server.use(/* scoped overrides */)
}))
按优先级划分的规则类别
| 优先级 | 类别 | 影响 | 前缀 | 规则数 |
|---|
| 1 | 处理器设计 | 严重 | handler- | 4 |
| 2 | 设置与生命周期 | 严重 | setup- | 3 |
| 3 | 请求读取 | 高 | request- | 2 |
| 4 | 响应构建 | 高 | response- | 3 |
| 5 | 测试模式 | 高 | test- | 4 |
| 6 | GraphQL | 中 | graphql- | 2 |
| 7 | 工具 | 中 | util- | 2 |
全部 20 条规则
处理器设计(严重)
| 规则 | 文件 | 摘要 |
|---|
使用 http 命名空间 | handler-use-http-namespace.md | v2 已移除 rest,请使用 http.get()、http.post() |
| URL 中不包含查询参数 | handler-no-query-params.md | 谓词中的查询参数会被剥离,导致处理器匹配过宽的路径 |
| v2 解析器签名 | handler-resolver-v2.md | 使用 ({ request, params, cookies }),而非 (req, res, ctx) |
| v2 响应构建 | handler-response-v2.md | 使用 HttpResponse.json(),而非 res(ctx.json()) |
设置与生命周期(严重)
| 规则 | 文件 | 摘要 |
|---|
| 正确的导入路径 | setup-import-paths.md | 服务端使用 msw/node,worker 使用 msw/browser |
| 生命周期钩子 | setup-lifecycle-hooks.md | 始终使用 beforeAll/afterEach/afterAll 模式 |
| 文件组织 | setup-file-organization.md | 在 src/mocks/ 中组织 handlers、node 和 browser 文件 |
请求读取(高)
| 规则 | 文件 | 摘要 |
|---|
| 在事件中克隆请求 | request-clone-events.md | 在生命周期事件中读取请求体前先克隆请求 |
| 异步读取请求体 | request-body-async.md | 始终执行 await request.json()——读取请求体是异步操作 |
响应构建(高)
| 规则 | 文件 | 摘要 |
|---|
| Cookie 使用 HttpResponse | response-use-httpresponse.md | 原生 Response 会丢弃 Set-Cookie,请使用 HttpResponse |
| 网络错误 | response-error-network.md | 使用 HttpResponse.error();不要抛出普通 Error |
| 流式传输 | response-streaming.md | Server-Sent Events 使用 sse();分块传输使用 ReadableStream |
测试模式(高)
| 规则 | 文件 | 摘要 |
|---|
| 测试行为 | test-behavior-not-requests.md | 断言 UI/状态,而非 fetch 调用参数 |
| 每个测试单独覆盖 | test-override-with-use.md | 对错误和边缘情况测试使用 server.use() |
| 并发隔离 | test-concurrent-boundary.md | 将并发测试包装在 server.boundary() 中 |
| 未处理的请求 | test-unhandled-request.md | 设置 onUnhandledFrame: 'error' |
GraphQL(中)
| 规则 | 文件 | 摘要 |
|---|
| 响应结构 | graphql-response-shape.md | 通过 HttpResponse.json 返回 { data } / { errors } |
| 端点范围限定 | graphql-scope-with-link.md | 多个 GraphQL API 请使用 graphql.link(url) |
实用工具(中)
| 规则 | 文件 | 摘要 |
|---|
| bypass 与 passthrough | util-bypass-vs-passthrough.md | bypass() = 新建请求;passthrough() = 放行请求 |
| 延迟行为 | util-delay-behavior.md | 在 Node.js 中,delay() 和 delay('real') 不会产生可观察到的延迟——请使用明确的毫秒数 |
响应方法速查
| 方法 | 用途 |
|---|
HttpResponse.json(data, init?) | JSON 响应 |
HttpResponse.text(str, init?) | 纯文本 |
HttpResponse.html(str, init?) | HTML 内容 |
HttpResponse.xml(str, init?) | XML 内容 |
HttpResponse.formData(fd, init?) | 表单数据 |
HttpResponse.arrayBuffer(buf, init?) | 二进制数据 |
HttpResponse.error() | 网络错误 |
v1 到 v2 迁移速查
| v1 | v2 |
|---|
import { rest } from 'msw' | import { http, HttpResponse } from 'msw' |
rest.get(url, resolver) | http.get(url, resolver) |
(req, res, ctx) => res(ctx.json(data)) | () => HttpResponse.json(data) |
req.params | 来自解析器信息的 params |
req.body | await request.json() |
req.cookies | 来自解析器信息的 cookies |
res.once(...) | http.get(url, resolver, { once: true }) |
res.networkError() | HttpResponse.error() |
ctx.delay(ms) | await delay(ms) |
ctx.data({ user }) | HttpResponse.json({ data: { user } }) |
v2 到 v3 迁移速查
MSW 3.0.0(2026-09-28)仅支持 ESM,并要求 Node.js >=22 和 TypeScript >=5.9(也支持 v6/v7)。主要变化:所有导出项都从包根目录移至子路径入口。完整指南(以及官方 codemod)请参阅 references/migration-v2-to-v3.md。
| v2 | v3 |
|---|
import { http, HttpResponse } from 'msw' | import { http, HttpResponse } from 'msw/http' |
import { graphql } from 'msw' | import { graphql } from 'msw/graphql' |
import { ws } from 'msw' / import { sse } from 'msw' | msw/ws / msw/sse |
import { delay } from 'msw' | import { delay } from 'msw/utils/delay' |
import { bypass, passthrough } from 'msw' | msw/utils/bypass、msw/utils/passthrough |
命名空间上的 graphql.query(...) | 已移除——改为以 link 为先:graphql.link(url).query(...) |
import { setupServer } from 'msw/native' | @msw/react-native 包取代 msw/native 入口 |
handleRequest() | 已移除——请使用 getResponse()(或使用实验性的 defineNetwork() 自定义网络) |
onUnhandledRequest: 'error' | onUnhandledFrame: 'error'——涵盖请求和 WebSocket 连接 |
自定义回调 (request, print) | 单个 { frame, defaults } 对象;根据 frame.protocol 缩小范围,并调用 defaults.warn() |
server.events.on('connection', ...) | server.events.on('websocket:connection', ...)——仅限生命周期 API;ws link 自身的 addEventListener('connection') 保持原名 |
GraphQLLinkHandlers 类型 | 重命名为 GraphQLLink |
来自 msw 的 LifeCycleEventsMap | 来自 msw/experimental 的按协议划分的映射(HttpNetworkFrameEventMap、WebSocketNetworkFrameEventMap) |
worker.stop() 返回 void | 返回 Promise——对于有进行中关键任务的场景,请使用 await worker.stop() |
v3 新增:msw/vite 插件(用于提供 worker 脚本——无需再将 mockServiceWorker.js 复制到 public 目录)、GraphQL v17 和 link 优先的订阅、WebSocket 扩展 API、页面导航和表单提交拦截、passthrough 请求标头修改,以及用于生成更小包的独立 msw/utils/* 模块。
参考资料
| 参考资料 | 涵盖内容 |
|---|
handler-api.md | http.* 和 graphql.* 方法、URL 谓词、路径参数 |
response-api.md | HttpResponse 类、所有静态方法、Cookie 处理 |
server-api.md | setupServer/setupWorker、生命周期事件、boundary() |
test-patterns.md | Vitest/Jest 设置、覆盖、并发隔离、缓存清理 |
migration-v1-to-v2.md | v1 到 v2 的所有破坏性变更和迁移指南 |
migration-v2-to-v3.md | v2 到 v3 的所有破坏性变更、新 API 和迁移指南 |
anti-patterns.md | 10 个常见错误及 BAD/GOOD 示例 |