mcpField
W
web-mocks-msw

web-mocks-msw

github.com/agents-inc测试与质量2671 次安装2 天前更新
代码仓库

关于 web-mocks-msw

web-mocks-msw 是一项使用 MSW 在开发和测试中模拟网络请求的指南技能。它说明如何在浏览器和 Node 环境共用处理器,并管理响应变体与测试专用覆盖。

主要功能

  1. 01浏览器和 Node 共用处理器
  2. 02可复用且有类型的响应数据
  3. 03默认、空和错误响应变体
  4. 04按测试覆盖处理器
  5. 05模拟延迟和运行时变体

适用场景

  1. 01后端尚未就绪时模拟 API
  2. 02测试空、错误或延迟响应
  3. 03为单项测试覆盖一个端点
  4. 04开发时切换响应变体

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-mocks-msw

安装后在 Claude Code 中输入/web-mocks-msw确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
examples/
browser.md
core.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>

评论

支持 Markdown