Prisma 中文文档 下载 App

种子数据(seeding)与测试隔离

什么是 seeding

种子数据是指在空数据库里插入一批初始记录——比如:

配置 seed 脚本

在 package.json 里告诉 Prisma 怎么跑 seed:

{
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  }
}

用 TypeScript 写 seed 脚本(需要 ts-node 或 tsx):

// prisma/seed.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // 创建默认角色
  await prisma.role.createMany({
    data: [
      { name: 'USER', description: '普通用户' },
      { name: 'ADMIN', description: '管理员' },
      { name: 'MODERATOR', description: '版主' },
    ],
    skipDuplicates: true,
  })

  // 创建示例用户
  const admin = await prisma.user.upsert({
    where: { email: 'admin@example.com' },
    update: {},
    create: {
      email: 'admin@example.com',
      name: '系统管理员',
      role: 'ADMIN',
    },
  })

  // 创建示例文章
  for (let i = 0; i < 10; i++) {
    await prisma.post.upsert({
      where: { title: `示例文章 ${i + 1}` },
      update: {},
      create: {
        title: `示例文章 ${i + 1}`,
        content: `这是第 ${i + 1} 篇示例文章的内容...`,
        published: true,
        authorId: admin.id,
      },
    })
  }

  console.log('种子数据已创建 ✅')
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect())

注意用 upsert / createMany + skipDuplicates

种子脚本需要可重复执行。如果你反复跑,每次都从头 seed,重复插入会抛唯一冲突错误。解决方案:

这样 npm run seed 可以随时跑,不会重复插入也不会报错。

什么时候会自动跑 seed

命令是否自动执行 seed
npx prisma migrate dev不会(只在 reset 后会)
npx prisma migrate reset会(reset 完会自动跑 seed)
npx prisma db push不会
npx prisma db seed(单独执行)会
# 手动单独执行 seed
npx prisma db seed

测试隔离

在单元测试里直接用开发数据库会污染数据。推荐两种隔离方式:

方式一:测试前 reset + seed(推荐)

// jest.setup.ts 或 vitest 配置里
import { execSync } from 'child_process'

beforeAll(async () => {
  // 每次测试前重置数据库并跑 seed
  execSync('npx prisma migrate reset --force', {
    env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL },
  })
})

需要给测试单独准备一个数据库:

# .env.test
DATABASE_URL="postgresql://test:test@localhost:5432/testdb"

测试时用 dotenv -e .env.test 或 cross-env 注入。

方式二:测试内用事务隔离(更快)

每个测试用独立事务,测完自动回滚:

// src/__tests__/setup.ts
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()

// 在全局 beforeAll 里开启一个外层事务
let tx: any

beforeAll(async () => {
  // 用 db push 保证测试数据库结构最新
  execSync('npx prisma db push', {
    env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL },
  })
})

beforeEach(async () => {
  // 每个测试开始前开启一个事务
  tx = await prisma.$transaction(async (t) => t, { isolationLevel: 'Serializable' })
})

afterEach(async () => {
  // 每个测试结束后回滚(不提交)
  await tx.$queryRawUnsafe('ROLLBACK')
})

这种方式比 reset 快得多(不需要每次建表),但 Prisma Client 的事务嵌套支持有限,需要额外配置。

方式三:Prisma + Testcontainers

Docker Testcontainers 会在测试开始时起一个临时数据库容器,测试完销毁。隔离最彻底,但要求 CI 有 Docker 支持。

Prisma 的 seeding vs 其他 ORM

很多 ORM(TypeORM、Sequelize)有专门的 seeder 框架(TypeORM Seeder、Sequelize CLI seed)。Prisma 的设计更简单——就是 prisma.seed 指定一个普通的 Node.js 脚本,脚本里你想用什么 Prisma Client 方法就用什么。灵活,零框架负担。