📚 全栈开发学习系列
从零到全栈 · 8 个阶段 · 24+ 篇
✅ 阶段一:编程基础 01-08
路线总览 · Python · C语言 · 数据结构 · Git · Linux
✅ 阶段二:Web开发基础 09-16
HTML · CSS · JavaScript · HTTP · FastAPI · PostgreSQL · 认证授权 · 综合实战
阶段三:前端深化 17-24 · 进行中
✓ 17 React入门:组件化思想与JSX语法
✓ 18 React进阶:状态管理与副作用钩子
✓ 19 TypeScript入门:类型系统与类型注解
✓ 20 Next.js基础:SSR与路由体系
✓ 21 Tailwind CSS入门:实用优先的CSS框架
✓ 22 前端工程化:Vite与Webpack构建工具
✓ 23 前端测试:Vitest与Playwright
▸ 24 API测试:接口自动化与Mock服务(当前)

前端测试保证了"按钮点了有反应",但后端 API 返回的数据对不对?字段名变了前端会不会崩?分页参数传错会不会查出 10 万条记录?

这些问题靠手动 Postman 截图无法持续覆盖。本篇用 supertest 7.2 做 HTTP 接口断言、MSW 2.15 做 Mock 服务拦截,将 API 测试纳入 CI 流水线。读完本篇你将能为任意 RESTful 接口编写自动化测试、模拟后端未完成的接口、在测试中注入超时和错误场景。

📋 目录
1. API测试的三个层次
2. supertest 7.2:HTTP断言库
3. 编写第一个接口测试
4. 测试FastAPI接口实战
5. 断言响应体与状态码
6. MSW 2.15:Mock Service Worker
7. 编写Handler拦截请求
8. MSW与Vitest集成测试
9. MSW与Playwright集成
10. API测试最佳实践

1. API测试的三个层次

API测试像一个邮递系统的质检——信件(请求)投入邮筒后,分拣中心(路由)是否正确归类、投递时长(响应时间)是否达标、信件内容(响应体)是否完好无损、收件人签名(认证)是否有效。每个环节都需要独立验证。

三个层次各有侧重:

层次 验证目标 工具 典型场景
契约测试 接口字段/类型符合约定 MSW + TypeScript 前端开发时后端未完成
集成测试 API + 数据库协作正确 supertest + Vitest 后端开发验证路由逻辑
E2E测试 前后端联调完整流程 MSW + Playwright 页面→API→渲染全链路

契约测试保证前后端"说同一种语言"——字段名、类型、嵌套结构一致。集成测试验证后端 API 自身的业务逻辑。E2E 测试确保前端能正确消费 API 返回的数据。

💡 小贴士
前后端并行开发时,先用 MSW 模拟后端接口让前端跑起来,后端完成后切换到真实 API——代码不改一行,只改 Mock 开关。这就是"契约先行"开发模式。

2. supertest 7.2:HTTP断言库

supertest 是 Node.js 生态最成熟的 HTTP 测试库,基于 superagent 构建。当前最新版本 7.2.2(2026年5月发布)。它的核心思路:把 HTTP 请求当作测试断言链——request(app).get('/api').expect(200),一行代码完成请求 + 验证。

安装(在已有 Vitest 的项目中):

bash
npm install -D supertest @types/supertest
# supertest 依赖 superagent,npm 会自动安装
# @types/supertest 提供 TypeScript 类型支持

supertest 可以直接传入 Express/Fastify 应用的实例,无需启动真实 HTTP 服务器——它在内存中调用路由处理函数,速度极快。

3. 编写第一个接口测试

先准备一个 Express 应用作为被测对象:

src/app.ts
1
2
3
4
5
6
7
8
9
10
import express from 'express'
const app = express()
app.use(express.json())
app.get('/api/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() })
})
export default app

测试文件:

src/app.test.ts
1
2
3
4
5
6
7
8
9
10
11
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import app from './app'
describe('GET /api/health', () => {
  it('returns 200 with status ok', async () => {
    const res = await request(app).get('/api/health')
    expect(res.status).toBe(200)
    expect(res.body.status).toBe('ok')
  })
})

关键点:

4. 测试FastAPI接口实战

supertest 是 Node.js 工具,测试 FastAPI(Python)需要换个思路。两种方案:

方案 工具 适用场景
TestClient FastAPI 内置 Python 生态内测试
supertest + HTTP Node.js 调真实端口 跨语言端到端

Python 侧用 TestClient(基于 httpx):

tests/test_api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_list_users():
    response = client.get("/api/users")
    assert response.status_code == 200
    assert isinstance(response.json(), list)
def test_create_user():
    response = client.post("/api/users", json={"name": "Alice"})
    assert response.status_code == 201

如果要在 Node.js 侧用 supertest 测试 FastAPI,需要先启动 FastAPI 的真实 HTTP 服务器,然后让 supertest 连接对应端口——这属于 E2E 范畴,日常开发中 Python 测试用 TestClient 更高效。

5. 断言响应体与状态码

supertest 支持链式断言,也可以和 Vitest 的 expect 配合使用。两种风格对比:

src/users.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import app from './app'
describe('POST /api/users', () => {
  it('creates a user and returns 201', async () => {
    const res = await request(app)
      .post('/api/users')
      .send({ name: 'Bob', email: 'bob@test.com' })
      .set('Authorization', 'Bearer token123')
    expect(res.status).toBe(201)
    expect(res.body).toEqual({
      id: expect(any(Number)),
      name: 'Bob',
      email: 'bob@test.com'
    })
  })
})

常用链式方法:

⚠️ 常见错误:忘记 await 导致测试"假通过"

错误写法:const res = request(app).get('/api')(缺少 await)

原因:supertest 返回 Promise,不加 await 时 res 是 Promise 对象而非响应体。res.status 是 undefined,toBe(200) 失败但 Vitest 可能在 Promise resolve 前就结束测试,报"假通过"。所有 supertest 调用必须加 await。

6. MSW 2.15:Mock Service Worker

MSW(Mock Service Worker)是一个 API 模拟层,通过 Service Worker 拦截浏览器发出的网络请求并返回模拟数据。当前最新版本 2.15.0(2026年7月发布)。MSW 2.x 重构了 Handler 语法,采用标准 Fetch API 的 Request/Response 对象。

MSW 的核心优势:同一套 Mock 定义在浏览器和 Node.js 中都能用。开发时用浏览器拦截,测试时用 Node.js 拦截,生产构建时不打包——一份代码两个环境。

安装:

bash
npm install -D msw
# 初始化 Service Worker 文件(浏览器端用)
npx msw init public/ --save

7. 编写Handler拦截请求

Handler 是 MSW 的核心——定义"哪个请求用什么响应回复"。MSW 2.x 使用 http 命名空间和 HttpResponse 对象:

src/mocks/handlers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { http, HttpResponse } from 'msw'
export const
export const handlers = [
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ])
  }),
  http.post('/api/users', async ({ request }) => {
    const body = await request.json()
    return HttpResponse.json(
      { id: 3, ...body },
      { status: 201 }
    )
  }),
  // 模拟服务器错误
  http.get('/api/error', () => {
    return new HttpResponse(null, { status: 500 })
  })
]

Handler 解析:

💡 小贴士
Handler 中的路径 /api/users 是相对路径,MSW 会自动匹配同源请求。也可以用绝对 URL 拦截跨域请求:http.get('https://api.example.com/users'),这在测试第三方 API 时非常有用。

8. MSW与Vitest集成测试

在 Vitest 中使用 MSW,需要用 setupServer 创建 Node.js 端的 Mock 服务器:

src/mocks/server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const
export const server = setupServer(...handlers)
// 在测试入口文件中启动和关闭
import { beforeAll, afterAll, afterEach } from 'vitest'
import { server } from './mocks/server'
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

三个生命周期的作用:

测试中使用 server.use() 为单个测试覆盖 Handler:

TypeScript
it
it('handles 500 error', async () => {
  server.use(http.get('/api/users', () => new HttpResponse(null, { status: 500 })))
  const res = await fetch('/api/users')
  expect(res.status).toBe(500)
})

server.use() 添加的 Handler 仅对当前测试生效——afterEach 中的 resetHandlers() 会在下一个测试前清除它。

9. MSW与Playwright集成

在 Playwright E2E 测试中,MSW 使用浏览器端的 setupWorker 而非 Node.js 端的 setupServer。原理:页面加载时初始化 Service Worker,拦截浏览器发出的真实请求。

e2e/msw.setup.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
import { test } from '@playwright/test'
test.beforeEach(async ({ page }) => {
  // 页面加载前注入 MSW worker
  await page.route('**/*', async route => {
    const url = route.request().url()
    if (url.includes('/api/')) {
      await route.fulfill({ json: { mocked: true } })
    } else {
      await route.continue()
    }
  })
})

Playwright 的 page.route() 提供了更简洁的请求拦截方式——无需 Service Worker 文件,直接在测试代码中拦截。如果项目已用 MSW 管理大量 Handler,也可以通过 page.addInitScript() 注入 MSW worker。

10. API测试最佳实践

场景 推荐工具 理由
后端接口自测 supertest + Vitest 内存调用,无需端口
前端开发后端未完成 MSW setupWorker 浏览器拦截,开发即用
前端单元/集成测试 MSW setupServer + Vitest Node.js 拦截,与单元测试同环境
E2E 流程验证 MSW / page.route + Playwright 模拟网络异常和边缘场景
Python/FastAPI 测试 TestClient + pytest Python 原生生态
⚠️ 常见错误:MSW Handler 路径不匹配

错误现象:MSW 已启动但请求未被拦截,真实 API 仍然发出。

原因:Handler 中的路径与实际请求 URL 不完全匹配。MSW 2.x 默认精确匹配路径。如果 API 有 baseURL(如 http://localhost:3000/api/users),Handler 写 /api/users 即可匹配同源请求;跨域请求必须写完整 URL。调试方法:在 Handler 回调中加 console.log(request.url) 确认拦截是否触发。

分级练习

基础(理解概念)
1. 用 supertest 为一个 GET /api/products 接口编写测试,验证返回 200 状态码且响应体是数组。
2. 编写 MSW Handler 模拟 GET /api/products 返回 3 条商品数据。
进阶(实际应用)
3. 创建一个 Express 应用含 POST /api/login 接口(接收 email/password,返回 JWT),用 supertest 测试正确密码返回 200、错误密码返回 401。
4. 用 MSW setupServer 在 Vitest 中模拟该登录接口,用 server.use() 注入 500 错误场景,验证前端错误处理逻辑。
挑战(综合实战)
5. 搭建完整的 API 测试体系:为一个博客系统同时编写 supertest 后端测试(文章 CRUD + 认证中间件)和 MSW 前端 Mock(开发模式用 setupWorker,测试模式用 setupServer)。在 package.json 中配置 test:api(supertest)、test:unit(Vitest + MSW)、test:e2e(Playwright + page.route)三个脚本,实现三层测试全覆盖。
📌 知识回顾
API测试三层 supertest 7.2.2 request(app).get() TestClient MSW 2.15.0 http.get / http.post HttpResponse.json setupServer setupWorker page.route server.use() 契约先行
下一篇预告
25 状态管理:Zustand 与服务端状态
前端深化阶段的收尾篇。从 React 内置的 useState/useContext 到 Zustand 的轻量全局状态,再到 TanStack Query 的服务端状态管理——当组件树变深、API 数据需要缓存和同步时,选择正确的状态管理方案至关重要。