⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

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/data
json
// 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

最佳实践

  1. 使用与生产相同的数据库类型:避免 SQLite 与 PostgreSQL 的行为差异
  2. 每个测试文件使用独立事务:保证测试隔离
  3. CI 中使用 Docker 服务:确保环境一致性
  4. 本地使用 Docker Compose:简化环境管理

本节小结

测试数据库的选择需要权衡速度与一致性。推荐使用与生产相同的数据库类型(如 PostgreSQL),通过 Docker 管理本地和 CI 环境。Mock 方案适合纯单元测试,但不应替代真实数据库的集成测试。