核心在于 await 必须置于请求发起后、断言前,并确保 mock 服务支持按需返回异常载荷流(如 chunked transfer encoding),而非简单抛异常;需用 express/next.js 分段 write + destroy、playwright route.continue() + 自定义代理、msw 配合手动 abort readablestream 实现;测试中须用 response.body.getreader() 显式读取并捕获 typeerror 或 aborterror,配合 abortcontroller 防挂起;通过请求头(x-mock-mode)或 query 参数(mock_stream=chunked&error_at=2)传递异常意图;断言需覆盖网络层(typeerror)、协议层(status===0 && type==='opaque')、应用层(reader.read() 提前 done:true)三类信号。

核心在于把 await 用在请求发起之后、断言之前,且确保 mock 服务本身支持按需返回异常载荷流(如 chunked transfer encoding 或 server-sent events),而不是简单抛异常或返回固定错误体。
确保 mock 服务能真实输出异常载荷流
多数 mock 工具(如 MSW、Playwright route.fulfill、或自建 Node.js mock server)默认返回完整 JSON 响应体。要模拟“异常载荷流”,需主动构造流式响应:
- 使用 Express/Next.js API Route 时,调用 res.write() 分段写入,中途调用 res.destroy() 或触发 socket error,模拟连接中断或传输异常
- 在 Playwright 中,route.fulfill() 不支持流式响应,需改用 route.continue_() + 自定义代理服务转发并注入流式错误
- MSW 的 rest.delay() 只控制延迟,不控制流行为;若需流式异常,需搭配自定义 ctx.fetch() 并手动 reject 或 abort ReadableStream
在测试脚本中用 await 等待可中断的 fetch 流
标准 fetch 返回 Response 对象,其 body 是 ReadableStream。要捕获流中途中断或解析失败,不能只 await fetch(),而要显式读取 body:
- 用 response.body.getReader() 获取 reader,再循环 reader.read(),并在某次 read() 中捕获 TypeError: Failed to fetch 或 AbortError
- 配合 AbortController 设置超时,使 await 在流卡住时主动退出,避免无限挂起
- 不要对整个 fetch 链直接 await response.json() —— 这会掩盖流式中断细节;应先检查 response.ok 和 response.headers.get('content-type'),再决定是否解析
与分布式 mock 服务协同的关键参数传递
本地测试脚本需告诉远端 mock 服务“这次我要流式异常”,不能靠全局开关。推荐通过请求头或 query 参数传递意图:
- 加 header:X-Mock-Mode: stream-error-408,mock 服务据此触发超时流中断
- 带 query:?mock_stream=chunked&error_at=2,表示第 2 个 chunk 后断开
- 避免用 URL path 区分(如 /api/user/error),因路径常被缓存或路由规则覆盖,header/query 更可控
断言阶段要验证流式异常特征,而非仅状态码
成功模拟 ≠ 返回 500。真正验证“异常载荷流”需检查三类信号:
- 网络层信号:fetch 抛出 TypeError(非 Response),说明连接被重置或 DNS 失败
- 协议层信号:Response.status === 0 且 response.type === 'opaque',常见于 CORS 预检失败或跨域中断
- 应用层信号:成功拿到 Response 但 body 流提前终止(reader.read() 返回 {done: true} 早于预期 chunk 数)











