5.1 响应模拟:状态码、响应体、响应头、延迟
本章把"回信"这件事讲透。配合内嵌的场景参数实验室,所有效果都能立刻试。
状态码全家桶
HttpResponse.json({ id: 1 }, { status: 201 }); // 创建成功
new HttpResponse(null, { status: 204 }); // 删除成功,无响应体
HttpResponse.json(body, { status: 400 }); // 参数错误
HttpResponse.json(body, { status: 401, headers: {
"WWW-Authenticate": "Bearer",
}}); // 未认证
HttpResponse.json(body, { status: 403 }); // 无权限
HttpResponse.json(body, { status: 404 }); // 资源不存在
HttpResponse.json(body, { status: 409 }); // 冲突
HttpResponse.json(body, { status: 422 }); // 语义校验失败
HttpResponse.json(body, { status: 429, headers: {
"Retry-After": "30",
}}); // 限流
HttpResponse.json(body, { status: 500 }); // 服务端内部错误
HttpResponse.json(body, { status: 503, headers: {
"Retry-After": "60",
}}); // 服务暂不可用本教程提供一个"万能状态码端点",路径里的数字即返回码:
任意状态码:GET /api/status/:code
把路径改成 401、404、451、503 各试一次 —— 前端组件对状态码的分支逻辑就是这样测全的。
统一错误信封
好的 mock 应该与真实后端约定一致。本教程所有错误都返回:
{ "error": { "code": "VALIDATION_FAILED", "message": "邮箱格式不正确", "details": {} } }function fail(status: number, code: string, message: string) {
return HttpResponse.json({ error: { code, message } }, { status });
}前端可以稳定地写 body.error.code 分支,切换到真实后端时无需改动。
响应头
const res = HttpResponse.json(pdfMeta, {
headers: {
"Content-Disposition": 'attachment; filename="report.pdf"',
"X-Request-Id": requestId,
"Cache-Control": "no-store",
},
});常见模拟目标:
| 响应头 | 模拟场景 |
|---|---|
Location | 201 创建后指向新资源 |
WWW-Authenticate | 401 要求认证方案 |
Retry-After | 429 限流 / 503 维护 |
Content-Disposition | 文件下载名 |
Set-Cookie | 下发/清除会话 Cookie |
X-Total-Count | 分页总数(无包壳风格 API) |
各种响应体
// JSON(最常用)
HttpResponse.json(data);
// 文本
new HttpResponse("ok", { headers: { "Content-Type": "text/plain" } });
// HTML / XML
new HttpResponse("<html>维护中</html>", {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
// 二进制:用 Uint8Array / Blob / ArrayBuffer
const png = await createPngBytes();
new HttpResponse(png, { headers: { "Content-Type": "image/png" } });
// 空响应
new HttpResponse(null, { status: 204 });
// 网络失败(不是状态码,而是 fetch reject)
HttpResponse.error();本站可直接体验二进制下载(浏览器会触发文件保存)与网络中断:
下载 SVG(观察 Content-Type / Content-Disposition)
网络失败:HttpResponse.error()
延迟模拟:100ms – 2000ms
慢接口是前端最常漏测的场景(loading 闪现、竞态、按钮连点)。MSW 有三种延迟写法:
import { delay, HttpResponse } from "msw";
// 1) 精确延迟(毫秒)
await delay(1200);
return HttpResponse.json(data);
// 2) 拟真模式:按 MSW 内置的真实网络延迟分布随机等待
await delay(); // 等价于 await delay("real")
await delay("infinite"); // 永不返回(挂起请求,测试超时/取消逻辑)
// 3) 本教程做法:全局场景延迟,所有 handler 读取同一配置(100–2000 可调)
await delay(scenario.latencyMs);v3 的
delay()不再接收{ min, max }对象:无参即拟真随机分布, 需要明确区间时(如本教程 100–2000ms 滑块)自行传入计算好的毫秒数。
下面的场景实验室与教程、两个示例共享同一份全局配置(存在 localStorage): 拖动滑块在 100–2000ms 之间调节,保存后点"探测"对比实际等待时间。
建议的测试矩阵:100ms(快网)→ 300ms(典型 4G)→ 2000ms(弱网), 分别检查 loading 态、防抖、请求竞态(旧响应覆盖新响应)与取消按钮。
错误状态注入
场景实验室支持三种错误模式:
none:正常;force:每个走prelude()的业务请求都返回forceStatus(默认 500);random:约 25% 概率注入错误,用于发现"偶发失败"下的 UI 韧性问题。
切到 force + 状态码 503,保存后回到 第四章的控制台 发请求,会稳定收到 503;切回 none 即恢复。 这个设计演示了一个重要思想:场景配置本身也可以是一组 mock 端点(GET/PUT /api/scenario),控制面板因此无需与 MSW 运行时直接耦合。
动态响应:同一端点,千变万化
http.get("*/api/users/:id", ({ params }) => {
const user = db.users.find((u) => u.id === String(params.id));
if (!user) return HttpResponse.json({ error: { code: "NOT_FOUND" } }, { status: 404 });
return HttpResponse.json(user);
});分页、搜索、回显请求头、根据 Accept 切换 JSON/XML —— 响应可以由请求的任何部分决定,下一章展开。