9.3.2 测试数据放哪——测试数据库:内存数据库 vs 真实数据库
测试数据库的选择直接影响测试的速度、可靠性和与生产环境的一致性。
方案对比
| 特性 | 真实数据库 | 内存数据库 | Mock |
|---|---|---|---|
| 速度 | 慢 | 快 | 最快 |
| 一致性 | 高 | 中 | 低 |
| 配置复杂度 | 中 | 低 | 高 |
| 发现真实问题 | 高 | 中 | 低 |
| CI 配置 | 需要服务 | 简单 | 最简单 |
推荐策略
方案一:真实数据库(推荐)
使用与生产相同类型的数据库,确保测试结果可信。
typescript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'test' ? [] : ['query'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}bash
# .env.test
DATABASE_URL="postgresql://test:test@localhost:5432/myapp_test"Docker 快速启动测试数据库
yaml
# docker-compose.test.yml
version: '3.8'
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: myapp_test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql/datajson
// package.json
{
"scripts": {
"test:db:up": "docker-compose -f docker-compose.test.yml up -d",
"test:db:down": "docker-compose -f docker-compose.test.yml down",
"test": "npm run test:db:up && dotenv -e .env.test -- jest; npm run test:db:down"
}
}方案二:SQLite 内存数据库
适合快速原型和简单测试,但与 PostgreSQL 语法有差异。
prisma
// prisma/schema.test.prisma
datasource db {
provider = "sqlite"
url = "file::memory:?cache=shared"
}typescript
// 注意:SQLite 不支持某些 PostgreSQL 特性
// - 数组类型
// - JSON 操作
// - 某些日期函数局限性:
- 不支持 PostgreSQL 特有的功能
- 行为可能与生产环境不一致
- 不推荐用于复杂业务逻辑测试
方案三:使用 Prisma Mock
适合纯单元测试,不涉及真实数据库操作。
typescript
// __mocks__/@prisma/client.ts
import { PrismaClient } from '@prisma/client';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
export const prismaMock = mockDeep<PrismaClient>();
beforeEach(() => {
mockReset(prismaMock);
});
export { prismaMock as PrismaClient };typescript
// __tests__/services/user.service.test.ts
import { prismaMock } from '@/mocks/@prisma/client';
import { UserService } from '@/services/user.service';
describe('UserService', () => {
it('应返回用户', async () => {
const mockUser = { id: '1', email: 'test@example.com', name: 'Test' };
prismaMock.user.findUnique.mockResolvedValue(mockUser);
const result = await UserService.getById('1');
expect(result).toEqual(mockUser);
expect(prismaMock.user.findUnique).toHaveBeenCalledWith({
where: { id: '1' },
});
});
});测试数据库初始化
typescript
// test/setup-db.ts
import { execSync } from 'child_process';
import { prisma } from '@/lib/prisma';
export async function setupDatabase() {
// 运行迁移
execSync('npx prisma migrate deploy', {
env: { ...process.env },
});
// 验证连接
await prisma.$connect();
}
export async function teardownDatabase() {
await prisma.$disconnect();
}
export async function resetDatabase() {
// 按依赖顺序清理表
const tablenames = await prisma.$queryRaw<{ tablename: string }[]>`
SELECT tablename FROM pg_tables WHERE schemaname='public'
`;
for (const { tablename } of tablenames) {
if (tablename !== '_prisma_migrations') {
await prisma.$executeRawUnsafe(`TRUNCATE TABLE "${tablename}" CASCADE`);
}
}
}GitHub Actions 配置
yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: myapp_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/myapp_test
- run: npm test
env:
DATABASE_URL: postgresql://test:test@localhost:5432/myapp_test最佳实践
- 使用与生产相同的数据库类型:避免 SQLite 与 PostgreSQL 的行为差异
- 每个测试文件使用独立事务:保证测试隔离
- CI 中使用 Docker 服务:确保环境一致性
- 本地使用 Docker Compose:简化环境管理
本节小结
测试数据库的选择需要权衡速度与一致性。推荐使用与生产相同的数据库类型(如 PostgreSQL),通过 Docker 管理本地和 CI 环境。Mock 方案适合纯单元测试,但不应替代真实数据库的集成测试。
