3.1 setupWorker / setupServer 与三大命名空间
MSW 的 API 表面非常小:启动器 + 协议命名空间 + 响应构造器。本章给你一张完整地图。
启动器
ts
// 浏览器
import { setupWorker } from "msw/browser";
const worker = setupWorker(...handlers);
await worker.start();
// Node(Vitest / Jest / 任意脚本)
import { setupServer } from "msw/node";
const server = setupServer(...handlers);
server.listen();两者接收同样的 handler 数组,返回同构的 API:use()(追加 handler)、resetHandlers()、restoreHandlers()、close()。 这正是"一套 mock,开发与测试共用"的技术基础。
混合 HTTP 与 WS handler 时,数组类型注解用 AnyHandler[]:
ts
import type { AnyHandler } from "msw";
export const handlers: AnyHandler[] = [
http.get("*/api/users", () => HttpResponse.json([])),
chatHandler,
];http 命名空间
http.get/post/put/patch/delete/head/options/all,签名统一:
ts
http.method(predicate, resolver)
// predicate:字符串路径 / Path-to-RegExp 模板 / 通配源
http.get("*/api/users/:id", ({ params, request, cookies }) => {
const { id } = params; // 路径参数
const url = new URL(request.url);
const q = url.searchParams.get("q"); // 查询参数
const token = request.headers.get("authorization");
return HttpResponse.json({ id, q, token });
});resolver 接收的解析信息对象常用字段:
| 字段 | 内容 |
|---|---|
request | 标准 Request,可读 headers / json() / formData() / text() / arrayBuffer() |
params | 路径参数(如 /users/:id 的 id) |
cookies | 解析后的 Cookie 对象 |
requestId | 本次请求唯一 id(写日志/链路追踪很有用) |
http.all("*/api/*", resolver) 匹配该路径的所有方法,常用于统一鉴权守卫。
HttpResponse 响应构造器
ts
// JSON(默认 200,自动带 Content-Type: application/json)
HttpResponse.json({ ok: true });
// 自定义状态码与响应头
HttpResponse.json({ error: { code: "NO_AUTH" } }, {
status: 401,
headers: { "WWW-Authenticate": "Bearer" },
});
// 201 Created + Location 头(RESTful 最佳实践)
HttpResponse.json(user, { status: 201, headers: { Location: `/api/users/${user.id}` } });
// 文本 / XML / 任意 body
new HttpResponse("pong", { headers: { "Content-Type": "text/plain" } });
new HttpResponse("<rss/>", { headers: { "Content-Type": "application/xml" } });
// 二进制(文件下载)
new HttpResponse(new Uint8Array(pngBytes), {
headers: { "Content-Type": "image/png" },
});
// 网络层失败(断网、CORS 失败)——fetch 直接 reject
HttpResponse.error();graphql 命名空间(msw/graphql)
ts
import { graphql } from "msw/graphql";
const gql = graphql.link("*/graphql"); // 单端点;link 固定源与路径
// 按操作名匹配 query / mutation
export const listUsers = gql.query("ListUsers", ({ variables }) =>
HttpResponse.json({ data: { users: db.users.paginate(variables) } }),
);
export const createUser = gql.mutation("CreateUser", async ({ request, variables }) => {
if (!request.headers.get("authorization")) {
// GraphQL 标准错误信封
return HttpResponse.json({
errors: [{ message: "未登录", extensions: { code: "UNAUTHENTICATED" } }],
});
}
return HttpResponse.json({ data: { createUser: { user: create(variables.input) } } });
});要点:
graphql.query("操作名", resolver)按 operationName 分发,与 HTTP 方法无关;- resolver 必须内联
HttpResponse.json(...)(辅助函数返回类型会宽化,导致类型错误); - 查询参数 / 变更变量从
variables取,请求体仍可从request.json()拿; - 错误用标准
{ errors: [{ message, extensions: { code } }] }信封。
ws 命名空间(msw/ws)
ts
import { ws } from "msw/ws";
const chat = ws.link("ws://*/ws/chat");
export const chatHandler = chat.addEventListener("connection", ({ client, server }) => {
// client:浏览器侧的连接;server:handler 侧用于主动推消息
server.connect();
client.send(JSON.stringify({ type: "system", text: "欢迎来到信标驿站" }));
client.addEventListener("message", (event) => {
// 广播给所有在线 client;排除发送者用 chat.broadcastExcept(client, payload)
chat.broadcast(String(event.data));
});
});下一章会专门讲 Worker 生命周期,之后进入拦截与响应的细节。先用下面的控制台热热身: 第一个请求观察默认 300ms 延迟,第二个直接命中 503:
http.get 体验