一、为什么需要 Mock API
之所以需要 Mock API(接口模拟),本质是解决「真实后端接口未就绪/不可用」和「前端开发/测试被阻塞」的核心痛点,核心价值3点:
- 解耦前后端开发:后端还在开发接口、定接口文档,前端不用等,可以直接模拟接口返回的约定好的请求/响应数据,并行开发,大幅提升效率;
- 让测试「稳定+可控+无依赖」:真实接口有各种不可控问题——网络波动、接口返回随机数据、调用频次受限、依赖第三方服务,Mock 能固定返回预期数据,保证单元测试/集成测试的结果稳定、可复现;
- 低成本覆盖全场景:轻松模拟真实接口的异常场景(比如 401 未授权、404 找不到资源、500 服务报错、超时)和边界场景(比如超大返回数据、空数据),这些场景在真实接口中很难刻意构造。
二、主流 Mock API 方案:Jest Mock & MSW
这两个是前端最主流的2个Mock方案,定位不同、使用场景互补,先给你结论:
Jest Mock:「框架内置」的模块级模拟,属于「前端侧拦截」,适合单元测试(测单个函数/组件); MSW (Mock Service Worker):「独立专业」的请求层模拟,属于「网络侧拦截」,适合单元测试+集成测试+真实开发联调,是目前业界最优解。
方案一:Jest 内置的 Mock 功能(Jest Mock)
1. 核心定位
Jest 是前端主流的测试框架,Mock 是它的内置核心能力,不用额外装插件就能用。
2. 核心原理:「模块级拦截」- 只在JS执行层面模拟
Jest Mock 并不是真的「拦截网络请求」,而是在 JavaScript 模块导入/执行的层面,替换掉你项目中被调用的「请求模块/请求函数」。
- 比如你项目里用
axios.get('/api/user')发请求,Jest 会直接把axios.get这个函数「替换成一个模拟函数」; - 这个模拟函数不会发真实网络请求,只会按照你的配置,直接返回预设好的响应数据;
- 整个过程没有任何真实的HTTP请求发出,完全是JS代码层面的「假调用、真返回」。
3. 核心特点(优缺点)
优点:
- 零额外依赖,Jest 自带,开箱即用,配置简单;
- 模拟速度极快,无网络开销,适合纯单元测试;
- 能精准控制「某个函数/模块」的返回值,适合测试单个工具函数、组件的逻辑。
缺点(核心短板):
- 模拟范围有限:只能模拟「你项目代码里直接调用的模块」,如果你的请求逻辑封装在第三方库/深层模块里,模拟成本高;
- 无法覆盖全链路:比如你想在「真实浏览器环境」中调试页面,Jest Mock 完全失效(因为它只在 Jest 测试环境生效);
- 侵入性强:需要在测试代码中手动「mock 模块→调用函数→清除mock」,代码耦合度高。
4. 极简示例(模拟 axios 请求)
// 你的业务代码:api.js
import axios from 'axios'
export const getUser = () => axios.get('/api/user')
// 你的测试代码:api.test.js
import axios from 'axios'
import { getUser } from './api'
// 核心:模拟 axios 整个模块
jest.mock('axios')
test('测试 getUser 接口返回正确数据', async () => {
// 预设模拟返回值
axios.get.mockResolvedValue({ data: { name: '张三', id: 1 } })
// 调用业务函数
const res = await getUser()
// 断言结果正确
expect(res.data.name).toBe('张三')
// 断言 axios.get 被正确调用
expect(axios.get).toHaveBeenCalledWith('/api/user')
})
方案二:MSW (Mock Service Worker) 专业接口模拟工具
1. 核心定位
MSW = Mock Service Worker(模拟服务工作者),是一个独立、无框架依赖的专业 Mock 工具,也是目前前端社区公认的最佳 Mock 方案,没有之一。
支持服务端(node)Mock以及浏览器端Mock:
setupWorker: 浏览器端启动 MSW 的方法,用于开发环境setupServer: Node 端启动 MSW 的方法,用于测试环境
2. 核心原理:「网络层拦截」- 真正的「无缝模拟」
这是 MSW 和 Jest Mock 最核心的区别,也是 MSW 的灵魂:
MSW 是基于浏览器的 Service Worker 技术(浏览器的后台线程),在「网络请求发送到真实服务器之前」,在浏览器的网络层拦截所有 HTTP/HTTPS 请求。
通俗讲:你的项目代码里写的请求(axios/fetch/fetch API)完全不用改一行代码,该怎么发请求还是怎么发,MSW 会在「网络层面」把请求拦住,然后返回你预设的模拟数据,整个过程对「前端业务代码」是 100% 透明 的。
3. 核心特点
优点(压倒性优势,也是为什么成为最优解):
- 「无侵入」:业务代码零修改
你写的请求逻辑(比如
fetch('/api/user'))不用改任何代码,不管是测试环境还是开发环境,都能直接用,这是 Jest Mock 做不到的核心优势; - 「全场景覆盖」:一套配置,到处能用 同一份 Mock 配置,能同时用在:Jest/Vitest 单元测试、React/Vue 项目本地开发联调、CI自动化测试、真实浏览器调试,真正的「一次配置,多处复用」;
- 「模拟真实」:最贴近生产环境的请求行为 拦截的是真实的 HTTP 网络请求,会返回真实的状态码、响应头、响应体,能模拟所有真实接口的行为(包括跨域、超时、报错),比 Jest Mock 更贴近真实场景;
- 无框架绑定:支持 React/Vue/Angular/原生JS,支持 axios/fetch/任何请求库,甚至能模拟 GraphQL 接口。
缺点(几乎可以忽略的小问题):
- 需要额外安装依赖(
npm i msw -D),并做简单的初始化配置; - 首次使用需要理解 Service Worker 的基本概念,但配置一次后终身受用。
4. 服务端Mock示例
步骤1:安装依赖
npm install msw -D
步骤2:创建 Mock 配置文件(src/mocks/handlers.js)- 定义接口规则
// src/mocks/handlers.js
import { rest } from 'msw'
// 定义所有需要模拟的接口
export const handlers = [
// 模拟 GET /api/user 接口
rest.get('/api/user', (req, res, ctx) => {
// 返回模拟数据 + 200 状态码
return res(ctx.status(200), ctx.json({ name: '张三', id: 1 }))
}),
// 模拟 POST /api/login 接口(带参数)
rest.post('/api/login', (req, res, ctx) => {
const { username } = req.body
if (username === 'admin') {
return res(ctx.json({ token: 'admin-token', success: true }))
}
// 模拟登录失败的异常场景
return res(ctx.status(401), ctx.json({ success: false, msg: '用户名错误' }))
}),
]
步骤3:初始化 MSW(src/mocks/server.js)- 启动拦截服务
// src/mocks/server.js
import { setupServer } from 'msw/node' // 测试环境用 node 版本
import { handlers } from './handlers'
// 创建并启动模拟服务
export const server = setupServer(...handlers)
步骤4:在测试/开发中使用
// 你的业务代码:api.js(完全不用改)
export const getUser = () => fetch('/api/user').then(res => res.json())
// 你的测试代码:api.test.js
import { server } from './src/mocks/server'
import { getUser } from './api'
// 测试前启动模拟服务
beforeAll(() => server.listen())
// 测试后重置所有 mock 规则
afterEach(() => server.resetHandlers())
// 测试结束关闭服务
afterAll(() => server.close())
test('测试 getUser 接口', async () => {
const user = await getUser()
expect(user.name).toBe('张三') // 直接断言,无需模拟任何模块
})
5.浏览器端Mock示例
以 React/Vite 项目为例,实现开发环境的接口 Mock。
步骤1:定义 Mock 接口处理器(handlers)
创建 src/mocks/handlers.js,编写接口 Mock 规则:
// src/mocks/handlers.ts
import { rest } from 'msw'
// 定义全局的基础URL(可选,方便统一管理)
const BASE_URL = 'http://localhost:3000/api'
export const handlers = [
// 1. Mock GET 请求:获取用户信息
rest.get(`${BASE_URL}/user/:id`, (req, res, ctx) => {
// 从请求参数中获取用户ID
const userId = req.params.id
// 根据参数动态返回数据
if (userId === '1') {
return res(
ctx.status(200), // 设置响应状态码
ctx.headers({
'Content-Type': 'application/json',
'X-Custom-Header': 'msw-mock'
}), // 设置响应头
ctx.json({
id: '1',
name: '张三',
age: 28,
role: 'admin'
}) // 设置响应体
)
}
// Mock 404 场景
return res(
ctx.status(404),
ctx.json({
message: '用户不存在'
})
)
}),
// 2. Mock POST 请求:用户登录
rest.post(`${BASE_URL}/login`, async (req, res, ctx) => {
// 解析请求体(需 await,因为 req.json() 是异步方法)
const { username, password } = await req.json()
// 模拟登录成功逻辑
if (username === 'admin' && password === '123456') {
return res(
ctx.status(200),
ctx.json({
token: 'msw-token-123456',
success: true,
message: '登录成功'
})
)
}
// 模拟登录失败逻辑
return res(
ctx.status(401),
ctx.json({
success: false,
message: '用户名或密码错误'
})
)
}),
// 3. Mock 异常场景:500 服务器错误
rest.get(`${BASE_URL}/error`, (req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
message: '服务器内部错误'
})
)
}),
// 4. Mock 延迟响应:模拟网络延迟
rest.get(`${BASE_URL}/delay`, (req, res, ctx) => {
return res(
ctx.delay(2000), // 延迟 2 秒返回
ctx.status(200),
ctx.json({
message: '延迟响应成功'
})
)
})
]
步骤2:配置 MSW 启动脚本
创建 src/mocks/browser.ts,用于浏览器端启动 MSW:
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
// 创建 Service Worker 实例
export const worker = setupWorker(...handlers)
步骤3:在项目入口启动 MSW
修改项目入口文件(如 src/main.tsx),仅在开发环境启动 MSW:
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
// 开发环境启动 MSW
if (import.meta.env.DEV) {
const { worker } = await import('./mocks/browser.ts')
// 启动 Mock 服务
worker.start({
// 未配置的请求,放行到真实服务器
onUnhandledRequest: 'bypass'
})
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
验证效果
启动项目后,在浏览器控制台会看到 MSW 的启动日志:
[MSW] Mocking enabled.
此时请求 http://localhost:3000/api/user/1,会直接返回 Mock 数据,无需修改任何业务请求代码。
三、Jest Mock vs MSW 核心区别
| 对比维度 | Jest Mock (Jest内置Mock) | MSW (Mock Service Worker) |
|---|---|---|
| 拦截层级 | JavaScript 模块执行层 | 浏览器/Node 网络请求层 |
| 核心本质 | 替换「被调用的函数/模块」,无真实请求 | 拦截「真实的HTTP请求」,返回模拟数据 |
| 业务代码侵入性 | 高:需要手动mock模块,耦合测试代码 | 无:业务请求代码零修改 |
| 适用场景 | 纯单元测试(测试单个函数/组件逻辑) | 单元测试+集成测试+本地开发联调+CI测试 |
| 复用性 | 低:Mock配置只能在Jest测试中用 | 极高:一套配置,所有环境通用 |
| 学习成本 | 低(Jest内置,语法简单) | 中(需要理解Service Worker,配置一次即可) |
| 模拟真实性 | 一般(仅模拟函数返回,无真实请求特征) | 极高(真实HTTP请求特征,状态码/响应头齐全) |
四、选型建议
不用纠结,按你的需求直接选,99%的场景都适用:
场景1:只做「纯单元测试」(比如测试一个工具函数、无请求的组件)
选 Jest Mock 就够了,简单、快、零额外依赖。
场景2:需要测试「带接口请求的组件/页面」、「本地开发联调接口」、「全链路测试」
无脑选 MSW!这是最优解,也是现在大厂的主流选型,虽然多一步配置,但带来的效率提升是质的飞跃。
场景3:进阶用法(最佳实践)
Jest + MSW 组合使用:Jest 负责模拟「本地模块/工具函数」,MSW 负责模拟「所有接口请求」,各司其职,完美覆盖所有测试场景。
总结
- Mock API 的核心价值:解耦前后端、让测试稳定可控、低成本覆盖所有接口场景;
- Jest Mock 是「模块级模拟」,适合纯单元测试,简单但有侵入性;
- MSW 是「网络级模拟」,无侵入、全场景覆盖,是前端 Mock 的终极方案;
- 记住:只要涉及接口请求的模拟,优先用 MSW。