使用 Docker 进行 TypeScript 集成测试:从 A 到 Z
现代 TypeScript 开发不仅需要高质量的单元测试,还需要在真实环境中验证组件间的交互。使用 Docker 进行集成测试可以启动包含数据库、缓存和其他服务的隔离环境,使测试尽可能接近生产环境。在本文中,我们将探讨如何使用 Docker 配置 TypeScript 应用程序的集成测试,为 REST API 编写测试,并介绍最佳实践。
为什么选择 Docker 进行集成测试?
Docker 解决了集成测试的主要问题之一——对外部服务的依赖。我们不是使用可能被其他开发人员占用的公共数据库或 Redis,而是为每次测试运行启动隔离的容器。这保证了:
- 测试的洁净性:每次运行都从空状态开始
- 可重复性:测试在任何机器上运行结果一致
- 隔离性:测试之间互不影响,也不影响生产数据
- 速度:容器在几秒钟内启动
项目配置与 Docker Compose
让我们创建一个使用 Express 和 PostgreSQL 的简单 TypeScript 项目。我们的目标是测试创建用户的端点。从 docker-compose.test.yml 文件开始:
version: '3.8'services: db_test: image: postgres:15-alpine environment: POSTGRES_DB: testdb POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass ports: - "5433:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"] interval: 5s timeout: 5s retries: 5
app_test: build: context: . dockerfile: Dockerfile.test environment: DATABASE_URL: postgres://testuser:testpass@db_test:5432/testdb NODE_ENV: test ports: - "3001:3000" depends_on: db_test: condition: service_healthy现在创建 Dockerfile.test 来构建 TypeScript 应用程序:
FROM node:18-alpineWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionCOPY . .RUN npm run buildCMD ["node", "dist/index.js"]使用 Jest 和 Supertest 编写集成测试
安装必要的包:
npm install --save-dev jest @types/jest ts-jest supertest @types/supertest创建 Jest 配置(jest.config.ts):
export default { preset: 'ts-jest', testEnvironment: 'node', testMatch: ['**/__tests__/**/*.test.ts'], setupFilesAfterSetup: ['./jest.setup.ts'], testTimeout: 30000,};jest.setup.ts 文件将负责启动和停止 Docker 容器:
import { execSync, spawn } from 'child_process';import path from 'path';
const composeFile = path.resolve(__dirname, '../docker-compose.test.yml');
beforeAll(async () => { // 启动容器 execSync(`docker-compose -f ${composeFile} up -d --build`, { stdio: 'inherit' }); // 等待应用程序可用 await new Promise((resolve) => setTimeout(resolve, 5000));});
afterAll(async () => { // 停止并删除容器 execSync(`docker-compose -f ${composeFile} down -v`, { stdio: 'inherit' });});现在编写测试本身。创建文件 __tests__/users.test.ts:
import request from 'supertest';
const BASE_URL = 'http://localhost:3001/api';
describe('Users API Integration Tests', () => { const testUser = { name: '伊万·伊万诺夫', email: 'ivan@example.com', password: 'securePass123' };