引言:为什么在 TypeScript 中进行测试是必不可少的
TypeScript 是一个强大的工具,它为 JavaScript 添加了静态类型检查。然而,即使是最严格的类型系统也无法保证你的代码在运行时能正确工作。错误、逻辑缺陷、边界情况处理不当——所有这些都不在编译器的检查范围之内。这时,测试就派上了用场。
在本文中,我们将介绍 TypeScript 中测试的三个主要层次:单元测试、集成测试和端到端测试 (e2e)。你将了解它们之间的区别、何时使用哪种测试,并看到真实的代码示例。阅读完后,你将能够为你的 TypeScript 项目搭建一个完整的测试流程。
1. 单元测试:检查代码的原子单元
单元测试是最快、成本最低的验证层次。它们将单个函数、方法或类隔离开来,并在没有外部依赖(数据库、API、文件系统)的情况下对其进行测试。
1.1 选择工具与配置
TypeScript 最流行的测试框架是 Jest。它通过 ts-jest 或 @swc/jest 内置了对 TypeScript 的支持。
// 安装npm install --save-dev jest @types/jest ts-jest
// 初始化配置npx ts-jest config:init之后,根目录下会出现一个包含配置的 jest.config.js 文件。
1.2 函数的单元测试示例
假设我们有一个用于计算折扣的简单函数:
// discount.tsexport function applyDiscount(price: number, discountPercent: number): number { if (discountPercent < 0 || discountPercent > 100) { throw new Error('Invalid discount percentage'); } return price - (price * discountPercent) / 100;}我们来为它编写一个单元测试:
// discount.test.tsimport { applyDiscount } from './discount';
describe('applyDiscount', () => { it('应该正确计算10%的折扣', () => { expect(applyDiscount(1000, 10)).toBe(900); });
it('折扣为0%时应返回原价', () => { expect(applyDiscount(500, 0)).toBe(500); });
it('折扣大于100%时应抛出错误', () => { expect(() => applyDiscount(100, 150)).toThrow('Invalid discount percentage'); });});运行:npx jest。所有三个测试应该都通过并显示绿色。
1.3 模拟与桩代码
如果一个函数访问了外部服务(例如数据库),我们可以用一个模拟对象来替换它:
// userService.test.tsimport { getUser } from './userService';import { db } from './database';
jest.mock('./database'); // 自动模拟
test('getUser 根据ID返回用户', async () => { (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Ivan' }); const user = await getUser(1); expect(user.name).toBe('Ivan');});2. 集成测试:检查模块间的交互
集成测试检查系统的不同部分如何协同工作:控制器、服务、仓库、数据库。它们比单元测试慢,但能更好地确保连接的正确性。
2.1 使用 Express 测试 API 端点
为了测试 HTTP 请求,我们将 Supertest 库与 Jest 结合使用。
// 安装npm install --save-dev supertest @types/supertest假设我们有一个 Express 应用:
// app.tsimport express from 'express';import { createUser } from './controllers/userController';
export const app = express();app.use(express.json());app.post('/api/users', createUser);集成测试:
// app.test.tsimport request from 'supertest';import { app } from './