种子数据(seeding)与测试隔离
什么是 seeding
种子数据是指在空数据库里插入一批初始记录——比如:
- 默认的系统管理员账号
- 测试用的示例数据(方便调试)
- 应用启动时需要的基础配置数据(比如 roles、feature_flags)
配置 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,重复插入会抛唯一冲突错误。解决方案:
createMany({ skipDuplicates: true })— 遇到重复跳过upsert({ where: 唯一条件, update: {}, create: {...} })— 存在就什么都不做
这样 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 方法就用什么。灵活,零框架负担。