关于 web-mocks-msw
web-mocks-msw 是一项使用 MSW 在开发和测试中模拟网络请求的指南技能。它说明如何在浏览器和 Node 环境共用处理器,并管理响应变体与测试专用覆盖。
主要功能
- 01浏览器和 Node 共用处理器
- 02可复用且有类型的响应数据
- 03默认、空和错误响应变体
- 04按测试覆盖处理器
- 05模拟延迟和运行时变体
适用场景
- 01后端尚未就绪时模拟 API
- 02测试空、错误或延迟响应
- 03为单项测试覆盖一个端点
- 04开发时切换响应变体
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-mocks-msw安装后在 Claude Code 中输入/web-mocks-msw确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlexamples/browser.mdcore.md详细介绍
MSW 模式
快速指南: MSW 在网络层拦截请求,因此应用代码不会察觉请求被模拟。两种环境共用一组处理器——开发环境使用
msw/browser中的setupWorker,测试中使用msw/node中的setupServer,两者不可互换。将响应正文放在单独的模块中,以便重复使用每种变体(默认、空、错误),并依据 API 生成的类型进行类型检查。
详细资源:
- examples/core.md — 模拟数据模块、变体处理器、服务设置和测试生命周期、单个测试的覆盖、运行时变体切换、模拟延迟
- examples/browser.md — 浏览器 worker 设置,以及客户端渲染和服务端渲染入口点的应用集成
适用哪种方式
处理器共用;只有设置模块和生命周期不同。
- 浏览器,在开发期间 — 使用
msw/browser中的setupWorker,在应用渲染前等待其启动,并通过运行时变体切换让 UI 展示各种状态。请参阅 examples/browser.md。 - Node,在测试中 — 使用
msw/node中的setupServer,为测试套件启动监听,并在测试之间重置;使用server.use()覆盖单个测试的行为。请参阅 examples/core.md。
<critical_requirements>
编写 MSW 代码前
根据环境选择匹配的设置函数。 setupWorker 需要 Service Worker API,而 setupServer 会修补 Node 的请求层,因此在另一种环境中使用时都会失败;报错会指出缺少的全局对象,而不是提示两者用反了。
在 afterEach 中使用 server.resetHandlers() 重置处理器,这样 server.use() 添加的覆盖就不会影响下一个测试的结果。
在渲染前等待 worker.start() 完成。 如果 worker 尚未就绪就发出请求,请求会到达真实网络,导致测试套件的首次渲染有时与后续渲染不同。
将响应正文放在单独的模块中,并依据 API 生成的类型进行类型检查——这样一个 fixture 就能供多个处理器使用,架构变更也会在编译时而非断言过程中暴露。
</critical_requirements>
自动检测: msw、setupWorker、setupServer、msw/browser、msw/node、http.get、http.all、HttpResponse.json、server.use、resetHandlers、onUnhandledRequest、mockServiceWorker.js、delay()
适用于:
- 在后端尚未就绪时,于开发过程中模拟 HTTP 响应
- 无需更改应用代码即可测试空响应、错误响应和慢响应
- 让开发服务器和测试套件共用一组处理器
- 为单个测试覆盖单个端点
其他部分处理:
- 测试运行器配置和生命周期钩子——本技能说明
beforeAll和afterEach中应放什么,而非由哪个运行器提供这些钩子 - 渲染组件并查询结果
- 与真实后端集成;此时被测对象是服务端自身的行为
MSW 模拟的是网络,而不是调用网络的代码。应用中不会注入、包装或替换任何内容,因此测试中运行的代码就是生产环境中运行的代码——同一组处理器也可以驱动开发服务器、测试套件和演示构建。
但这只有在处理器集描述的是 API,而非某个测试的需求时才成立。数据放在 fixture 中,处理器选择 fixture,而单个测试所需的任何特殊行为都通过覆盖实现,并在之后丢弃。
<decision_framework>
更改响应的机制
| 你想要实现的效果 | 使用 |
|---|---|
| 多个测试共用一个场景 | 在默认处理程序旁导出一个具名变体处理程序 |
| 让某个测试看到不同的内容 | server.use(variant()) — 调用 resetHandlers() 后移除 |
| 开发时手动切换状态 | 由默认处理程序在请求时读取的变体映射 |
| 让被测代码等待的响应 | 使用明确时长的 delay(ms) |
运行时变体切换仅适用于开发。在测试中,它是共享的可变状态,会在设置它的测试结束后继续存在;而 server.use() 的作用域仅限当前测试,且会自行清理。
</decision_framework>
核心模式
模式 1:将响应正文放在独立模块中
夹具与处理程序分开存放,并根据 API 生成的类型进行类型检查,这样一个正文可以供多个处理程序使用,架构变更也会以类型错误的形式显现。
// mocks/features.ts
import type { GetFeaturesResponse } from "./api-types";
export const defaultFeatures: GetFeaturesResponse = {
features: [{ id: "1", name: "Dark mode", status: "done" }],
};
export const emptyFeatures: GetFeaturesResponse = { features: [] };
确实只属于某个测试的数据仍直接写在该测试中。
完整代码:examples/core.md
模式 2:变体处理程序
在同一个模块中导出默认处理程序和每个备选场景,这样测试和开发服务器就能按名称选择变体。
import { http, HttpResponse } from "msw";
export const getFeaturesHandlers = {
defaultHandler: () =>
http.get(ENDPOINT, () => HttpResponse.json(defaultFeatures)),
emptyHandler: () =>
http.get(ENDPOINT, () => HttpResponse.json(emptyFeatures)),
errorHandler: () =>
http.get(ENDPOINT, () => new HttpResponse("Server error", { status: 500 })),
};
HttpResponse.json 会设置 JSON 内容类型,并在 init.status 未另行指定时返回 200,因此只有错误变体需要声明状态码。非 JSON 正文——纯文本、空错误响应——则通过 new HttpResponse(body, init) 返回。
完整代码,包括在请求时读取变体映射的默认处理程序:examples/core.md
模式 3:一组处理程序,两种设置方式
处理程序数组共用;使用它的模块则根据环境选择。
// browser-worker.ts
import { setupWorker } from "msw/browser";
export const browserWorker = setupWorker(...handlers);
// server-worker.ts
import { setupServer } from "msw/node";
export const server = setupServer(...handlers);
完整代码,包含应用集成:examples/browser.md
模式 4:测试生命周期和逐测试覆盖
仅监听一次,在测试之间重置,并在最后关闭。server.use() 会将一个处理程序添加到前面,随后由 resetHandlers() 将其移除。
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it("renders the empty state", async () => {
server.use(getFeaturesHandlers.emptyHandler());
renderApp();
});
完整代码:examples/core.md
<red_flags>
注意事项
会在运行时出错:
- 在浏览器包中使用
setupServer,或在 Node 中使用setupWorker——错误会指出缺少全局变量,因此看起来像是环境问题,而不是导入对象用错 - 不使用
await就调用worker.start()——首批请求会与 worker 争抢,进而访问真实网络;这会表现为最先运行的某个规范测试间歇性失败 - 在服务器渲染的入口点顶层导入浏览器 worker——服务工作线程代码会进入服务器包并导致构建失败;应在
typeof window检查后动态导入 - 未在
afterEach中调用resetHandlers()——覆盖处理程序会在测试结束后继续生效,因此套件单独运行时通过,换个顺序运行时却失败 worker.start()未置于环境判断之后——服务工作线程会在生产包中注册,导致真实用户收到夹具数据
容易出乎意料的行为:
- 不带参数的
delay()会在浏览器中随机等待 100–400ms,在 Node 中则会被抵消,因此需要暂停的测试必须传入明确时长 server.use()添加的覆盖处理程序会一直存在,直到调用resetHandlers();不会在添加它的测试结束时失效http.all()会匹配路径上的所有方法,因此即使请求使用了错误的 HTTP 方法,也仍会得到成功响应once: true会让处理程序仅匹配一个请求,随后停止;这正是顺序响应所需的行为,也会导致后续请求悄然落空- 如果在
start/listen时未设置onUnhandledRequest,没有对应处理程序的请求就会静默放行,因此缺失的模拟会看起来像后端故障——可选值为"bypass"、"warn"或"error",分别对应静默、警告和致命错误
</red_flags>