创见博客
如何Mock一个API请求进行前端测试
七崽爱吃小饼干2026/01/19阅读 0专栏 React

一、为什么需要 Mock API

之所以需要 Mock API(接口模拟),本质是解决「真实后端接口未就绪/不可用」和「前端开发/测试被阻塞」的核心痛点,核心价值3点:

  1. 解耦前后端开发:后端还在开发接口、定接口文档,前端不用等,可以直接模拟接口返回的约定好的请求/响应数据,并行开发,大幅提升效率;
  2. 让测试「稳定+可控+无依赖」:真实接口有各种不可控问题——网络波动、接口返回随机数据、调用频次受限、依赖第三方服务,Mock 能固定返回预期数据,保证单元测试/集成测试的结果稳定、可复现;
  3. 低成本覆盖全场景:轻松模拟真实接口的异常场景(比如 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. 核心特点(优缺点)

优点:

  1. 零额外依赖,Jest 自带,开箱即用,配置简单;
  2. 模拟速度极快,无网络开销,适合纯单元测试;
  3. 能精准控制「某个函数/模块」的返回值,适合测试单个工具函数、组件的逻辑。

缺点(核心短板):

  1. 模拟范围有限:只能模拟「你项目代码里直接调用的模块」,如果你的请求逻辑封装在第三方库/深层模块里,模拟成本高;
  2. 无法覆盖全链路:比如你想在「真实浏览器环境」中调试页面,Jest Mock 完全失效(因为它只在 Jest 测试环境生效);
  3. 侵入性强:需要在测试代码中手动「mock 模块→调用函数→清除mock」,代码耦合度高。

4. 极简示例(模拟 axios 请求)

javascript
// 你的业务代码: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. 核心特点

优点(压倒性优势,也是为什么成为最优解):

  1. 「无侵入」:业务代码零修改 你写的请求逻辑(比如 fetch('/api/user'))不用改任何代码,不管是测试环境还是开发环境,都能直接用,这是 Jest Mock 做不到的核心优势;
  2. 「全场景覆盖」:一套配置,到处能用 同一份 Mock 配置,能同时用在:Jest/Vitest 单元测试、React/Vue 项目本地开发联调、CI自动化测试、真实浏览器调试,真正的「一次配置,多处复用」;
  3. 「模拟真实」:最贴近生产环境的请求行为 拦截的是真实的 HTTP 网络请求,会返回真实的状态码、响应头、响应体,能模拟所有真实接口的行为(包括跨域、超时、报错),比 Jest Mock 更贴近真实场景;
  4. 无框架绑定:支持 React/Vue/Angular/原生JS,支持 axios/fetch/任何请求库,甚至能模拟 GraphQL 接口。

缺点(几乎可以忽略的小问题):

  1. 需要额外安装依赖(npm i msw -D),并做简单的初始化配置;
  2. 首次使用需要理解 Service Worker 的基本概念,但配置一次后终身受用。

4. 服务端Mock示例

步骤1:安装依赖

bash
npm install msw -D

步骤2:创建 Mock 配置文件(src/mocks/handlers.js)- 定义接口规则

javascript
// 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)- 启动拦截服务

javascript
// src/mocks/server.js
import { setupServer } from 'msw/node' // 测试环境用 node 版本
import { handlers } from './handlers'

// 创建并启动模拟服务
export const server = setupServer(...handlers)

步骤4:在测试/开发中使用

javascript
// 你的业务代码: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 规则:

typescript
// 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:

typescript
// 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:

typescript
// 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 负责模拟「所有接口请求」,各司其职,完美覆盖所有测试场景。


总结

  1. Mock API 的核心价值:解耦前后端、让测试稳定可控、低成本覆盖所有接口场景;
  2. Jest Mock 是「模块级模拟」,适合纯单元测试,简单但有侵入性;
  3. MSW 是「网络级模拟」,无侵入、全场景覆盖,是前端 Mock 的终极方案;
  4. 记住:只要涉及接口请求的模拟,优先用 MSW。
评论
0/100