eduardweb.
Prisma ORMIntermediar#prisma#ci-cd#database#testing

Cum facem seed-uri în Prisma care nu crapă în CI: Upsert-uri și date deterministice

De Cristian Barbu, 6 iul. 2026 · 16 vizualizări · 3 like-uri

Postat 6 iul. 2026
typescript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // 1. Seed pentru date statice (Idempotent prin upsert)
  const roles = ['ADMIN', 'EDITOR', 'USER']
  
  for (const role of roles) {
    await prisma.role.upsert({
      where: { name: role },
      update: {}, // Nu modificăm nimic dacă există deja
      create: { name: role },
    })
  }

  // 2. Utilizator fix pentru testele de integrare din CI
  await prisma.user.upsert({
    where: { email: 'test.admin@company.com' },
    update: {
      role: 'ADMIN'
    },
    create: {
      email: 'test.admin@company.com',
      name: 'CI Admin Test',
      role: 'ADMIN',
    },
  })
}

main()
  .catch((e) => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

Salutare! Cine n-a dat un npx prisma db seed în pipeline-ul de CI doar ca să vadă un mare ecran roșu din cauza unei constrângeri unice ridicate de un utilizator deja existent? Am pățit asta acum un an la un proiect cu vreo 120 de teste de integrare, unde CI-ul rula de fiecare dată pe o bază de date semi-murdară.

Problema cu seed-urile clasice e că majoritatea sunt scrise pentru un mediu ideal: o bază de date proaspăt creată și complet goală. În realitate, în CI sau pe mediile de staging, scriptul de seed va rula de zeci de ori peste aceleași tabele. Dacă folosești simple insert-uri, totul se va rupe rapid.

Idempotența e sfântă (De ce ne trebuie upsert)

Mulți developeri la început de drum scriu prisma.user.create(). E în regulă la prima rulare locală. A doua oară? Bum, Unique constraint failed on the fields: (email).

Soluția este să folosim mereu upsert. Da, scrii ceva mai mult boilerplate, dar salvează nopți pierdute. Definim un identificator unic stabil (de exemplu, un email de test fix) și mapăm update cu un obiect gol {} dacă nu vrem să suprascriem modificările făcute manual în timpul testelor, sau cu valorile actualizate dacă vrem să facem sync.

Faker-ul e bun, dar cu măsură

Am văzut echipe care generează 1000 de utilizatori complet randomizați cu @faker-js/faker direct în seed-ul de CI. E o capcană din două motive.

În primul rând, testele tale devin non-deterministice. Dacă testul X pică doar când Faker generează un nume de familie care conține un apostrof, o să pierzi ore întregi căutând un bug fantomă. În al doilea rând, durează o veșnicie. La un proiect trecut, generarea a 500 de entități cu relații complexe prin Prisma ne mânca aproape 40 de secunde din build-ul de CI.

Trade-off-ul pe care l-am adoptat:

  • Folosesc date statice, hardcodate pentru structurile de bază (roluri, planuri de abonament, utilizatori de test cu roluri specifice).
  • Dacă am nevoie de volum pentru teste de performanță, folosesc Faker, dar blochez seed-ul generatorului folosind faker.seed(123). Astfel, datele generate sunt mereu aceleași la fiecare rulare.

Cum structurăm un seed robust

În loc de un singur fișier uriaș de seed, prefer să le separ pe domenii (ex: seedRoles.ts, seedUsers.ts). Prisma caută un singur punct de intrare în package.json (de regulă ts-node prisma/seed.ts), așa că în acel fișier principal doar orchestrăm apelurile.

La final, regula mea de aur este simplă: rulează seed-ul de trei ori la rând pe mașina locală. Dacă a treia oară nu crapă nimic și baza de date arată exact ca după prima rulare, înseamnă că scriptul tău e pregătit pentru CI.

Cum gestionați voi datele de test în pipeline? Mergeți pe seed-uri rulate la fiecare build sau preferați să restaurați un dump SQL curat înainte de rularea suitei?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.