使用 Docker 进行 JavaScript 集成测试:完整指南
集成测试是 JavaScript (Node.js) 开发中至关重要的阶段。它检查您的应用程序与外部服务(如数据库、API、缓存和队列)的交互。使用 Docker 进行这些测试可以隔离测试环境,确保其可重复性,并避免"在我机器上能运行"的问题。在本文中,我们将介绍如何使用 Docker、Docker Compose 和流行的测试框架来设置 JavaScript 应用程序的集成测试。
为什么集成测试需要 Docker?
传统的集成测试经常遇到问题:不同开发人员的机器上的依赖项可能不同,数据库可能处于不同状态,环境设置需要花费数小时。Docker 通过提供以下功能解决了这些问题:
- 隔离性:每次测试运行都在一个干净的容器中进行。
- 可重复性:为所有开发人员和 CI/CD 提供相同的环境。
- 速度:容器在几秒内启动,而不是几分钟。
- 依赖管理:轻松启动 PostgreSQL、Redis、RabbitMQ 和其他服务。
使用 Docker Compose 设置测试环境
让我们从创建项目开始。假设我们有一个简单的 Node.js 应用程序,它将数据保存到 PostgreSQL。创建文件 docker-compose.test.yml:
version: '3.8'
services: test-runner: build: context: . dockerfile: Dockerfile.test depends_on: - db - redis environment: - NODE_ENV=test - DATABASE_URL=postgresql://testuser:testpass@db:5432/testdb - REDIS_URL=redis://redis:6379 volumes: - ./coverage:/app/coverage
db: image: postgres:15-alpine environment: POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass POSTGRES_DB: testdb ports: - "5432" tmpfs: - /var/lib/postgresql/data
redis: image: redis:7-alpine ports: - "6379"注意 PostgreSQL 的 tmpfs — 这确保测试完成后数据不会被保留,每次运行都从一个干净的数据库开始。
用于测试的 Dockerfile
创建 Dockerfile.test,它针对测试进行了优化,而不是生产环境:
FROM node:18-alpine
WORKDIR /app
# 仅复制 package.json 以缓存依赖层COPY package*.json ./RUN npm ci --only=production && npm ci --only=dev
# 复制源代码COPY . .
# 安装 wait-for-it 以等待服务就绪ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /wait-for-it.shRUN chmod +x /wait-for-it.sh
CMD ["sh", "-c", "/wait-for-it.sh db:5432 -- /wait-for-it.sh redis:6379 -- npm test"]编写 JavaScript 集成测试
现在让我们编写测试本身。使用 Jest 和 Supertest 来测试 API。创建文件 tests/integration/user.test.js:
const request = require('supertest');const { app } = require('../src/app');const { Pool } = require('pg');
describe('User API Integration Tests', () => { let pool;
beforeAll(async () => { // 创建到测试数据库的连接池 pool = new Pool({ connectionString: process.env.DATABASE_URL });
// 在测试前执行迁移 const migrationSQL = ` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); `; await pool.query(migrationSQL); });
afterAll(as