mswjs 是什么?
msw 是 Mock Service Worker的简写,是一个基于 Service Worker 实现的 API 模拟库,允许您编写与客户端无关的模拟。
当然它也是不是完美的,因为其基于 Service Worker 实现,所以其运行在 HTTPS 环境,或者在开发环境中运行于 localhost,无法在其他 HTTP 环境中使用详见mdn,官方其他限制说明
核心功能
拦截网络请求并返回模拟数据
支持 REST API、GraphQL API、WebSocket API 的模拟
可用于 浏览器环境 和 Node 环境
支持开发环境和测试环境使用, 其他模拟请求的库只能本地使用这个部署到线上后依然可以!
与其他库相比优势是什么?
使用标准的请求/响应 API
可以在开发工具中查看请求和响应
可以模拟网络延迟和错误情况
可以精确控制 API 响应
支持 headers、status code 等HTTP特性
可以模拟各种网络情况
不需要修改应用代码或配置代理,不需要启动真实服务器
提供 TypeScript 支持,有更好的类型提示
支持集成测试和端到端测试
使用场景
前端开发时后端 API 还未就绪
需要模拟复杂的 API 响应场景
编写可靠的前端测试用例
需要在离线环境开发调试
mswjs 使用
本次主要在浏览器环境中模拟 REST API、WebSocket API 请求, GraphQL API 如有需要后续补充!
我会使用 vite 初始化一个 vue+ts 的项目 猛击直达 github 仓库
安装 mswjs
执行 pnpm install msw@latest 安装 msw
执行 npx msw init ./public --save 创建 Worke 文件 ./public 是 mockServiceWorker.js 文件的存放目录
--save 参数会在 package.json 增加 mockServiceWorker.js 所在的目录,在下次安装 msw 依赖时会自动将 mockServiceWorker.js 拷贝到对应的目录中。
安装 @mswjs/data
@mswjs/data 是一个用于构建和管理模拟数据的库,提供了一种简单的方法来创建和管理这些模拟的后端数据。通常与 msw搭配使用。
主要功能和特点包括:
定义数据模型,类似于 ORM 中的实体定义。这让你能在前端定义数据库表的结构。
支持常见的数据库操作,如创建、读取、更新和删除(CRUD)一对多、多对对查询。
内存存储,数据存储在内存中,这适合在开发和测试环境中快速迭代,不会影响到真实的数据库。
执行 pnpm i @mswjs/data 安装,然后在后续的集成环节使用它。
集成 mswjs
在 src/mock 文件夹下创建下图中的对应文件
database.ts 数据模拟
使用 factory 创建一个 user 表同时导出 db 变量让外部使用,然后填充一些默认数据
ts
代码解读
复制代码
import { factory, primaryKey } from '@mswjs/data' export const db = factory({ user: { id: primaryKey(String), email: String, password: String, nickName: String, accountType: String, role: String, updatedAt: String, createdAt: String, avatar: String, }, }) // ===================================== 填充一些默认数据 ===================================== db.user.create({ id: '1bofj153qd3188su7qb00u5n6oh', email: 'admin@qq.com', password: '123456', nickName: 'kkf2Pg', accountType: '01', role: '01', updatedAt: '2024-07-28 22:04:04', createdAt: '2024-07-28 22:04:04', avatar: 'https://api.dicebear.com/7.x/bottts-neutral/svg?seed=kkf2Pg&size=64', }) db.user.create({ id: '1n3o6n1qh7d2uywa45y8100xweg0w', email: 'test@qq.com', password: '123456', nickName: 'kklpCj', accountType: '01', role: '01', updatedAt: '2024-07-21 13:28:33', createdAt: '2024-07-21 13:28:33', avatar: 'https://api.dicebear.com/7.x/bottts-neutral/svg?seed=kklpCj&size=64', })
utils.ts 工具函数
ts
代码解读
复制代码
import { HttpResponse } from 'msw' // 获取基础 path,需要部署到 github pages , mswjs-demo 仓库名 export function getBasePath() { return import.meta.env.PROD ? '/mswjs-demo' : '' } // 获取 api 的路径 export function getApiUrl(path: string) { return `${getBasePath()}/${path}` } // 返回的数据格式 export function sendJson(code: number, data: any, msg: string = '') { const info = { code, data, msg } return HttpResponse.json(info, { status: 200 }) } export function paginate
handlers.ts 模拟请求
使用 mswjs 支持 REST API 风格的请求, 其中 all 函数表示任何类型的请求都会被拦截官方文档直达
下边我们看一下如何使用 mswjs 实现用户 crud 的模拟。
ts
代码解读
复制代码
import type { UserInfo } from '@/api/user' import { http } from 'msw' import { v4 as uuidv4 } from 'uuid' import { db } from './database' import { getApiUrl, paginate, sendJson } from './utils' export const userHandlers = [ // 获取用户列表 http.post(getApiUrl('api/user/getList'), () => { const allList = db.user.getAll() /** * paginate 函数需要传入一个数组,返回一个分页后的数组 * 如果需要分页,需要在请求体中传入 pageNo 和 pageSize ,demo 就不做了 */ const data = { list: paginate(allList), pageNo: 1, pageSize: 10, total: allList.length, } // sendJson 统一返回数据格式的函数 return sendJson(0, data) }), // 创建用户 /** *
getApiUrl 因为这个 demo 最终需要部署到 gtihub pages 上,所以需要对 url 的路径进行一些处理!
index.ts 入口文件
导出加载 mswjs 的 initMswWorker 函数
ts
代码解读
复制代码
import { setupWorker } from 'msw/browser' import { allHandlers } from './handlers' import { getBasePath } from './utils' export const worker = setupWorker(...allHandlers) export function initMswWorker() { worker.start({ /** * onUnhandledRequest 指定如何处理未匹配的请求 * warn:打印警告但按原样执行请求。 * error:打印错误并停止请求执行。 * bypass:不打印任何内容,按原样执行请求。 */ onUnhandledRequest: 'bypass', serviceWorker: { /** * 指定 `mockServiceWorker` 文件的路径,路径不正确请求会 405 错误 * 因为这里最后需要把项目部署到 github pages 上 所以需要使用 `getBasePath`函数获取正确的路径 */ url: `${getBasePath()}/ats/mockServiceWorker.js`, }, }) }
然后在 main.ts 中引入
加载成功控制台会有如下提示:
加载失败控制台会有如下提示:
实现用户的 crud
api 请求
这里的 api 请求使用 axios 封装 猛击直达 github
crud 的页面
crud 的页面使用 @opentiny/vue 组件库构建, 具体代码如下
ts
代码解读
复制代码
查看 curd 效果
获取列表
增加用户:这里偷懒不做 ui 直接加数据
编辑用户:这里也偷懒了不做 ui 直接改数据
删除用户
实现 websocket 模拟
官方文档猛击直达
handlers.ts
在 src/mock/handlers.ts 中增加 wsHandlers 然后在 allHandlers 中统一导出
handlers 的代码不多,我们简单看一下!
首先是创建 websocket
创建使用 ws.link() 方法如下:
const wsChat = ws.link('ws://wsChat') 创建了一个模拟的 websocket 连接 ws://wsChat 是客户端连接的地址。
ws 类似于 http、wss 对应 https
ws.link() 执行后返回一些方法和变量 猛击查看官方文档
clients: clients: Set
返回所有客户端连接 addEventListener:可用于监听客户端连接、关闭等操作,下边会演示如何使用。
broadcast:向所有已连接的客户端发送数据
broadcastExcept:向除了当前客户端外的所有客户端发送消息
监听客户端连接
在 wsHandlers 中使用 chat.addEventListener('connection', ({ client }) => {}) 监听连接,client 是对应的客户端连接
ts
代码解读
复制代码
// 监听 ws 连接 wsChat.addEventListener('connection', ({ client }) => { // 首次连接向客户端发送连接成功的消息 client.send(createWsMsg('连接成功!')) // 监听客户端发送的消息 client.addEventListener('message', (event) => { // 向客户端发送消息 client.send(createWsMsg()) // 除了发送数据的客户端,所有已连接的客户端都将接收到发送的数据 wsChat.broadcastExcept(client, createWsMsg(`这是一条广播消息除了发送数据的客户端都会收到!${event.data}`)) // 所有已连接的客户端都将接收到发送的数据 wsChat.broadcast(createWsMsg('这是一条广播消息所有用户都会收到!')) console.warn('wsChat 收到的消息:', event.data) }) // 监听客户端断开连接 client.addEventListener('close', () => { wsChat.broadcast(createWsMsg(`客户端断开连接${client.id}`)) }) }),
client.id 每次连接都会生成新的 id 可以来做一些有意思的事情
createWsMsg 函数定义了返回的数据格式
ts
代码解读
复制代码
function createWsMsg(text?: string) { return JSON.stringify({ uid: uuidv4(), name: `wsChat`, data: text ?? `这是 wsChat 返回的消息 ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`, }) }
websocket 连接页面
使用 vueuse useWebSocket 来连接 websocket 发送消息代码如下:
ts
代码解读
复制代码
状态: {{ status }}
数据:
{
{ item.name }} : {
{ item.data }}
查看效果
发送消息
监听客户端连接关闭
mswjs 是怎么拦截请求的
mswjs 实现 nodejs 与浏览器的拦截请求的方式并不相同,我们简单了解其底层实现即可(作者没有深入研究)!
浏览器
浏览器的拦截请求是基于 Service Worker 实现
self.addEventListener('fetch', function (event) {...}) 是 Service Worker 中的一个事件监听器,用于拦截网页发出的网络请求并对其进行处理。
self: 代表当前的 Service Worker 实例。
addEventListener: 添加一个事件监听器,监听特定的事件。
fetch: 事件类型,当网页发起网络请求时触发。
event: 表示 fetch 事件的对象,包含与该请求相关的信息(如 URL 等)。
nodejs
nodejs 的请求拦截官方实现了一个低级的网络拦截库 @mswjs/interceptors
实现了对 HTTP、WebSocket 协议的拦截
FAQ
如果遇到问题
如何自定义响应信息?
使用 msw 导出的 HttpResponse 函数 具体猛击查看官方文档
其他
如果你在使用 vite 且只需要本地模拟请求 vite-plugin-mock-dev-server 或许也是不错的选择
原文链接:https://juejin.cn/post/7445926398400102440
可以通过 event.request 获取具体的请求信息。
使用 event.respondWith(response) 可以自定义返回的响应。
查看官方调试章节
在 github issue 寻求帮助