引言:为什么需要测试 TypeScript 代码?
TypeScript 是一个强大的工具,它为 JavaScript 添加了静态类型支持,让你能够在编译时捕获错误。然而,即使是最严格的类型系统也无法保证你的代码在实际环境中能够正确运行。逻辑错误、边界情况处理不当以及模块集成问题仍然超出了编译器的检查范围。这时,测试就派上了用场。
TypeScript 中的测试不仅仅是检查函数。它是一个全面的过程,包含三个关键级别:单元测试、集成测试和端到端测试。每个级别都有自己特定的任务,它们共同构成了确保应用程序可靠性的“测试金字塔”。
在本文中,我们将详细讲解如何使用现代工具(Jest、Supertest 和 Playwright)在 TypeScript 中编写所有三个级别的测试。你将了解这些测试有助于防止哪些错误,以及如何在你的项目中实现它们。
单元测试:检查隔离的模块
单元测试是金字塔的基础。它们隔离地测试单个函数、类或方法,不依赖外部依赖项(数据库、API、文件系统)。主要目标是确保应用程序的每一块“砖”都能正常工作。
工具和设置
TypeScript 中最流行的单元测试框架是 Jest。它通过 ts-jest 或 @swc/jest 提供对 TypeScript 的内置支持。
在项目中安装 Jest:
npm install --save-dev jest @types/jest ts-jestnpx ts-jest config:init单元测试示例
考虑一个计算折扣的简单函数:
// discount.tsexport function applyDiscount(price: number, discountPercent: number): number { if (discountPercent < 0 || discountPercent > 100) { throw new Error('Discount percentage must be between 0 and 100'); } return price - (price * discountPercent) / 100;}让我们编写覆盖主要场景的单元测试:
// discount.test.tsimport { applyDiscount } from './discount';
describe('applyDiscount', () => { it('should correctly apply a 20% discount', () => { expect(applyDiscount(100, 20)).toBe(80); });
it('should return the original price with a 0% discount', () => { expect(applyDiscount(50, 0)).toBe(50); });
it('should return 0 with a 100% discount', () => { expect(applyDiscount(200, 100)).toBe(0); });
it('should throw an error with a negative discount', () => { expect(() => applyDiscount(100, -10)).toThrow('Discount percentage must be between 0 and 100'); });
it('should throw an error with a discount greater than 100%', () => { expect(() => applyDiscount(100, 150)).toThrow('Discount percentage must be between 0 and 100'); });});重要提示:单元测试不应访问外部资源。而应使用模拟对象来替代依赖项,例如使用 jest.mock()。
集成测试:模块之间的交互
集成测试检查系统的不同部分如何协同工作。这可能是服务与数据库之间的交互、控制器与仓库之间的交互,或者微服务之间的交互。与单元测试不同,我们在这里不会完全隔离组件。
示例:测试 REST API
假设我们有一个带有一个端点的 Express 应用程序。为了测试 HTTP 请求,我们使用 Supertest。
// userController.tsimport { Router, Request, Response } from 'express';
interface User { id: number; name: string;}
const users: User[] = [{ id: 1, name: 'Alice' }];