9.2.3 测试前初始化数据库——数据库迁移:测试数据库的初始化
测试数据库的表结构必须与开发/生产保持一致,迁移是保证这一点的关键。
测试数据库初始化流程
使用 Prisma 管理测试数据库迁移
方法一:migrate deploy(推荐用于 CI)
bash
# 部署已有的迁移到测试数据库
dotenv -e .env.test -- npx prisma migrate deployjson
// package.json
{
"scripts": {
"test:setup": "dotenv -e .env.test -- prisma migrate deploy",
"test": "npm run test:setup && dotenv -e .env.test -- jest",
"test:ci": "npm run test:setup && dotenv -e .env.test -- jest --ci"
}
}方法二:migrate reset(开发时使用)
bash
# 重置数据库并重新运行所有迁移
dotenv -e .env.test -- npx prisma migrate reset --forcejson
// package.json
{
"scripts": {
"test:reset": "dotenv -e .env.test -- prisma migrate reset --force",
"test:fresh": "npm run test:reset && dotenv -e .env.test -- jest"
}
}方法三:db push(快速原型)
bash
# 直接推送 schema 变更(不生成迁移文件)
dotenv -e .env.test -- npx prisma db push自动化测试数据库初始化
typescript
// test/setup-db.ts
import { execSync } from 'child_process';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function setupTestDatabase() {
console.log('🔧 Setting up test database...');
try {
// 运行迁移
execSync('npx prisma migrate deploy', {
env: { ...process.env, DATABASE_URL: process.env.DATABASE_URL },
stdio: 'inherit',
});
// 验证连接
await prisma.$connect();
console.log('✅ Test database ready');
} catch (error) {
console.error('❌ Failed to setup test database:', error);
throw error;
}
}
export async function teardownTestDatabase() {
await prisma.$disconnect();
}typescript
// jest.setup.ts
import { setupTestDatabase, teardownTestDatabase } from './test/setup-db';
beforeAll(async () => {
await setupTestDatabase();
});
afterAll(async () => {
await teardownTestDatabase();
});Jest 全局配置
typescript
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
globalSetup: '<rootDir>/test/global-setup.ts',
globalTeardown: '<rootDir>/test/global-teardown.ts',
testTimeout: 30000, // 迁移可能需要较长时间
};
export default config;typescript
// test/global-setup.ts
import { execSync } from 'child_process';
export default async function globalSetup() {
console.log('\n🚀 Global test setup...');
// 确保测试数据库结构是最新的
execSync('dotenv -e .env.test -- npx prisma migrate deploy', {
stdio: 'inherit',
});
// 生成 Prisma Client
execSync('npx prisma generate', { stdio: 'inherit' });
}typescript
// test/global-teardown.ts
export default async function globalTeardown() {
console.log('\n🧹 Global test teardown...');
// 可选:清理测试数据库
}处理迁移冲突
场景:本地迁移与远程不一致
bash
# 查看迁移状态
dotenv -e .env.test -- npx prisma migrate status
# 如果有问题,重置测试数据库
dotenv -e .env.test -- npx prisma migrate reset --force场景:CI 中迁移失败
yaml
# .github/workflows/test.yml
jobs:
test:
steps:
- name: Setup Database
run: |
# 等待数据库就绪
sleep 5
# 运行迁移
npx prisma migrate deploy
env:
DATABASE_URL: ${{ env.DATABASE_URL }}使用 Docker 实现一次性数据库
typescript
// test/docker-db.ts
import { execSync } from 'child_process';
import { v4 as uuid } from 'uuid';
export function createTestDatabase() {
const dbName = `test_${uuid().replace(/-/g, '_')}`;
execSync(`docker run -d --name ${dbName} \
-e POSTGRES_DB=${dbName} \
-e POSTGRES_USER=test \
-e POSTGRES_PASSWORD=test \
-p 0:5432 \
postgres:15-alpine`);
// 获取映射的端口
const port = execSync(`docker port ${dbName} 5432`).toString().split(':')[1].trim();
return {
url: `postgresql://test:test@localhost:${port}/${dbName}`,
cleanup: () => execSync(`docker rm -f ${dbName}`),
};
}迁移策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| migrate deploy | CI/CD | 快速、可靠 | 需要已有迁移 |
| migrate reset | 开发测试 | 完全重置 | 较慢 |
| db push | 快速原型 | 最快 | 可能丢失数据 |
| Docker 一次性 | 并行测试 | 完全隔离 | 启动较慢 |
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 迁移超时 | 数据库连接慢 | 增加超时时间 |
| 表不存在 | 迁移未运行 | 检查 migrate deploy |
| 类型不匹配 | Client 未更新 | 运行 prisma generate |
| 并行冲突 | 共享数据库 | 使用事务或 Docker |
本节小结
测试数据库迁移的核心目标是确保测试环境的表结构与生产一致。推荐在 CI 中使用 migrate deploy,在本地开发时使用 migrate reset。通过 Jest 的全局配置,可以在测试运行前自动完成迁移,让测试专注于业务逻辑验证。
