前面两篇文章简单介绍了如何只用 Nextjs 在本地模拟 SSE 实现打字效果和通过 Streamdown 优雅地解析输出 AI 返回的 Markdown 文本,接下来就是如何测试了。

Mock 框架

Mock 框架有很多,个人倾向使用 Mock Service Worker (MSW) 来测 AI 相关的功能,在下面的详细对比中也有意将 MSW 放在第一位。

  • MSW (Mock Service Worker)
  • JSON Server
  • WireMock
  • Nock
  • MirageJS

框架对比

1. 架构设计与工作原理

框架 工作原理 优点 缺点
MSW 基于 Service Worker API 拦截网络请求 • 在网络层面拦截,透明度高
• 支持浏览器和 Node.js 环境
• 不侵入业务代码
• 需要理解 Service Worker 概念
• 在某些环境下配置相对复杂
JSON Server 启动独立的 REST API 服务器 • 零配置快速启动
• 自动生成 CRUD 接口
• 支持关系数据和查询
• 仅限于 REST API
• 功能相对简单
• 需要独立端口
WireMock 基于 HTTP 服务器的 Mock 框架 • 功能强大,支持复杂场景
• 丰富的匹配规则
• 支持状态管理和故障注入
• 主要面向 Java 生态
• 配置复杂
• 资源消耗较大
Nock HTTP 请求拦截库 • 专门针对 Node.js 环境
• API 简洁直观
• 测试友好
• 仅支持 Node.js
• 不支持浏览器环境
• 功能相对单一
MirageJS 客户端服务器模拟框架 • 提供完整的数据层模拟
• 支持数据库关系
• 内置 ORM
• 学习曲线陡峭
• 配置复杂
• 体积较大

2. 开发体验与易用性

框架 学习成本 配置复杂度 API 设计 TypeScript 支持 热重载 调试体验
MSW 中等 中等 声明式,直观 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
JSON Server 极低 极简 约定优于配置 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
WireMock 复杂 功能丰富但冗长 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
Nock 简单 链式调用,简洁 ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
MirageJS 复杂 ORM 风格 ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐

详细说明:

  • MSW: 声明式 API 设计优秀,TypeScript 支持完善,但 Service Worker 调试相对复杂
  • JSON Server: 学习成本最低,一行命令启动,但功能相对基础
  • WireMock: 企业级功能完善,但配置复杂,主要面向 Java 开发者
  • Nock: API 简洁明了,测试友好,但仅限 Node.js 环境
  • MirageJS: 功能最全面,但学习曲线陡峭,配置最复杂

3. 性能表现

框架 启动速度 内存占用 响应延迟 并发处理
MSW ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
JSON Server ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
WireMock ⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
Nock ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
MirageJS ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐

4. 功能覆盖度

功能特性 MSW JSON Server WireMock Nock MirageJS
REST API
GraphQL
WebSocket/SSE
浏览器环境
Node.js 环境
请求录制回放
故障注入
延迟模拟
状态管理 基础
数据持久化
数据关系建模 基础
自定义中间件
代理模式
TypeScript 支持 部分 部分
热重载

符号说明:

  • ✅:完全支持
  • 部分:部分支持
  • 基础:基础功能
  • ❌:不支持

5. 生态系统与社区支持

框架 GitHub Stars NPM 周下载量 社区活跃度 文档质量 维护状态
MSW 15.8k+ 1.2M+ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 积极维护
JSON Server 72k+ 800k+ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 稳定维护
WireMock 6.2k+ N/A ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 积极维护
Nock 12.6k+ 1.8M+ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 稳定维护
MirageJS 5.4k+ 50k+ ⭐⭐⭐ ⭐⭐⭐⭐ 维护缓慢

适合场景

🚀 适合 MSW 的场景:

  • 现代前端应用开发(React、Vue、Angular)
  • 同时支持开发和测试环境
  • 模拟实时数据流(SSE、WebSocket)
  • 统一的 Mock 解决方案

⚡ 适合 JSON Server 的场景:

  • 快速原型验证
  • 简单的 REST API Mock
  • 最小学习成本
  • 独立的 Mock 服务

🏢 适合 WireMock 的场景:

  • 企业级功能(录制回放、故障注入)
  • 微服务架构测试
  • 复杂的网络场景模拟
  • Java 技术栈集成

🧪 适合 Nock 的场景:

  • Node.js 后端测试
  • 精确的请求拦截验证
  • 轻量级测试解决方案
  • 与测试框架深度集成

🎯 适合 MirageJS 的场景:

  • 完整的数据层模拟
  • 复杂的数据关系建模
  • 长期前端项目开发
  • 离线开发能力

选择 MSW 的优势

  1. 架构先进: 基于 Service Worker 设计,在网络层面拦截请求,不侵入业务代码
  2. 跨环境支持: 同时支持浏览器和 Node.js 环境,一套代码多环境复用
  3. 开发体验: 优秀的 TypeScript 支持和现代化的 API 设计
  4. 功能完善: 支持 REST、GraphQL、实时数据流等多种协议
  5. 社区活跃: 持续的更新和丰富的生态系统

安装 MSW

演示基于 Nextjs

  • 安装 MSW

// npm npm i msw --save-dev // yarn yarn add msw -D

  • 额外的一步:生成浏览器端的 ./public/mockServiceWorker.js

npx msw init public/

使用 MSW

目录结构和文件解释


MSW SSE Mock 项目 - 目录结构 =============================== 项目根目录: /mock-sse-by-msw ├── 📁 public/ # 静态文件目录 │ └── 📄 mockServiceWorker.js # MSW Service Worker 脚本,拦截浏览器请求 ├── 📁 src/ # 源代码目录 │ ├── 📁 app/ # Next.js App Router 目录 │ │ ├── 📄 globals.css # 全局 CSS 样式,应用于整个应用程序 │ │ ├── 📄 layout.tsx # 根布局组件,包住所有页面 │ │ └── 📄 page.tsx # 主页面组件,展示 SSE 功能演示 │ ├── 📁 components/ # 可复用的 React 组件目录 │ │ └── 📄 mockServer.tsx # Mock 服务器初始化组件 │ └── 📁 mock/ # MSW Mock 配置和处理器 │ ├── 📁 __fixture__/ # 测试数据目录 │ │ └── 📄 summaryTexts.js # SSE 流式传输模拟的示例文本数据 │ ├── 📄 browser.ts # MSW 浏览器端设置,用于拦截客户端请求 │ ├── 📄 handler.ts # MSW 请求处理器,定义 Mock API │ ├── 📄 initmock.ts # 开发环境 Mock 初始化逻辑 │ └── 📄 server.ts # MSW 服务器端设置,用于 Node.js 环境(测试) └── 📄 yarn.lock # Yarn 依赖锁定文件,用于包管理

开发工作流

  1. MSW Service Worker 在浏览器中拦截网络请求
  2. Mock 处理器提供真实的 API 响应
  3. SSE 端点逐字符流式传输数据
  4. React 组件消费实时数据流
  5. 开发服务器提供热重载即时更新
    • 更改 ./src/mock/__fixture__/summaryText.js 中的 Markdown 数据

核心代码

在 MSW 中模拟 SSE (Server-Sent Events) handler


// ./src/mock/handler.ts export const handlers = [ // SSE (Server-Sent Events) handler http.get("/api/sse", ({ request }) => { const url = new URL(request.url); const interval =20; const summaryKey = url.searchParams.get('summary'); const maxCount = summaryKey && summaryArray[summaryKey].length || 10; const stream = new ReadableStream({ start(controller) { let counter = 0; const sendEvent = () => { let message = ""; // If summary key is provided and exists in summaryArray, use summary text if (summaryKey && summaryArray[summaryKey] && summaryArray[summaryKey][counter]) { message = summaryArray[summaryKey][counter]; } const data = { id: counter, message, timestamp: new Date().toISOString(), type: 'update', }; const eventData = `data: ${JSON.stringify(data)}\n\n`; controller.enqueue(new TextEncoder().encode(eventData)); counter++; if (counter < maxCount) { setTimeout(sendEvent, interval); // Send event with custom interval } else { // Send final event and close controller.enqueue(new TextEncoder().encode('data: {"type":"close","message":"Stream ended"}\n\n')); controller.close(); } }; // Send initial event sendEvent(); } }); return new HttpResponse(stream, { status: 200, headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control' } }); }) ]

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐