📚 全栈开发学习系列
从零到全栈 · 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')
})
})
|
关键点:
request(app) 接收 Express 实例,不启动真实端口
.get('/api/health') 链式调用指定 HTTP 方法
- 返回值
res 包含 status、body、headers,用 Vitest 的 expect 断言
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'
})
})
})
|
常用链式方法:
.send(body) — POST/PUT 请求体,自动序列化为 JSON
.set(key, val) — 设置请求头(如 Authorization)
.query(params) — URL 查询参数
.expect(status) — supertest 内置断言(可替代 expect)
⚠️ 常见错误:忘记 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 解析:
http.get('/api/users', handler) — 拦截 GET 请求
HttpResponse.json(data) — 返回 JSON 响应(自动设置 Content-Type)
{ request } — 解构获取请求体,可读取 POST 数据
{ status: 500 } — 第二参数控制状态码
💡 小贴士
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.listen() — 启动 Mock 服务器,拦截开始
server.resetHandlers() — 每个测试后重置临时 Handler,保证隔离
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 数据需要缓存和同步时,选择正确的状态管理方案至关重要。