nock TypeScript 教程:HTTP 模拟测试实战指南
1. 什么是 nock?为什么需要它?
nock 是一个强大的 Node.js 库,专门用于拦截和模拟 HTTP 请求。它允许开发者模拟外部服务(如 REST API、第三方微服务、Webhook)的响应,而无需实际发起网络请求。这在编写单元测试和集成测试时尤为重要,因为测试的稳定性和执行速度至关重要。
为什么需要它?假设你的应用需要向支付网关、天气 API 或通过 HTTP 访问数据库发送请求。在测试环境中,你不想依赖这些服务的可用性、响应速度或请求限制。nock 可以“替换”这些请求,返回预设的数据。这让测试变得快速、可预测且与外部因素隔离。此外,你还可以测试错误场景(超时、500 错误、空响应),这些在真实环境中很难复现。
在 TypeScript 环境中,nock 与流行的测试框架(Jest、Mocha、Vitest)完美集成,并支持类型定义,使开发更便捷、更安全。该库在 Node.js 层面(通过 http 和 https 模块)拦截请求,因此适用于任何 HTTP 客户端:axios、node-fetch、got,甚至原生的 fetch(需注意一些细节)。
2. 安装指南
要在 TypeScript 项目中安装 nock,请在终端中执行以下任一命令:
# 使用 npm
npm install --save-dev nock @types/nock
# 使用 yarn
yarn add --dev nock @types/nock
# 使用 pnpm
pnpm add --save-dev nock @types/nock
注意: @types/nock 包含 TypeScript 的类型声明。如果你使用 TypeScript 4.0 或更高版本,类型可能已内置在库中,但为了兼容性,建议显式安装。
3. 快速上手
让我们编写一个最小测试示例,展示 nock 的基本用法。假设我们有一个从外部 API 获取用户数据的函数。
// userService.ts
import axios from 'axios';
export async function getUser(userId: number): Promise<{ id: number; name: string; email: string }> {
const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
return response.data;
}
现在编写一个测试,模拟这个请求:
// userService.test.ts
import nock from 'nock';
import { getUser } from './userService';
describe('getUser', () => {
afterEach(() => {
nock.cleanAll(); // 每个测试后清除所有模拟
});
it('应该返回用户数据', async () => {
// 1. 配置拦截
const mockUser = { id: 1, name: '张三', email: 'zhangsan@example.com' };
nock('https://jsonplaceholder.typicode.com')
.get('/users/1')
.reply(200, mockUser);
// 2. 调用被测试函数
const user = await getUser(1);
// 3. 验证结果
expect(user).toEqual(mockUser);
});
it('应该在 500 错误时抛出异常', async () => {
nock('https://jsonplaceholder.typicode.com')
.get('/users/1')
.reply(500, { error: '服务器错误' });
await expect(getUser(1)).rejects.toThrow();
});
});
这个示例展示了如何轻松模拟服务器响应,并测试成功和错误两种场景。
4. 核心方法详解
nock 提供了丰富的方法来精确控制 HTTP 请求的模拟。以下是最常用的方法:
- nock(baseUrl):创建一个拦截器,匹配指定基础 URL 的请求。
- .get(path)、.post(path)、.put(path)、.delete(path):指定要拦截的 HTTP 方法和路径。
- .reply(statusCode, body, headers?):设置模拟响应的状态码、响应体和可选的头信息。
- .query(queryObject):匹配特定的查询参数。
- .times(n):限制拦截器被使用的次数。
- .persist():使拦截器持久化,可被多次使用。
- .delay(duration):模拟网络延迟。
例如,模拟一个带查询参数的 POST 请求:
nock('https://api.example.com')
.post('/data', { key: 'value' })
.query({ page: 1 })
.reply(201, { id: 123 });
5. 高级用法与最佳实践
在实际项目中,nock 可以处理更复杂的场景:
- 匹配请求体:使用
.post(path, body)精确匹配请求体内容。 - 动态响应:使用回调函数根据请求动态生成响应。
- 记录和重放:结合
nock.recorder记录真实请求,然后在测试中重放。 - 清理和重置:使用
nock.cleanAll()和nock.restore()确保测试隔离。
最佳实践:
- 在每个测试后调用
nock.cleanAll(),避免测试间相互影响。 - 使用
nock.isDone()验证所有预期的拦截器是否都被调用。 - 对于集成测试,考虑使用
nock.back模式,将真实请求记录为 fixture 文件。