Ir al contenido
angel.library

Los datos de prueba deben mostrar por qué un caso es especial. Una fixture gigantesca con veinte campos irrelevantes oculta la regla; datos mínimos y defaults válidos dejan visible la diferencia que activa el comportamiento.

TérminoPropósito
Fixtureestado preparado para ejecutar una prueba
Factoryfunción que crea una entidad válida
BuilderAPI que construye variantes de forma legible
Seedconjunto inicial para un ambiente o suite
Golden filearchivo con salida esperada revisable
Snapshotrepresentación guardada que se compara en ejecuciones futuras

Una factory puede producir una fixture; no todos los equipos usan los nombres igual. Lo importante es el alcance, el dueño y la limpieza.

type User = {
id: string
email: string
role: "member" | "admin"
active: boolean
}
let sequence = 0
export function userFactory(overrides: Partial<User> = {}): User {
sequence += 1
return {
id: `user_test_${sequence}`,
email: `user-${sequence}@example.test`,
role: "member",
active: true,
...overrides
}
}
it("impide administrar a un miembro", () => {
const user = userFactory({ role: "member" })
expect(canManageUsers(user)).toBe(false)
})

El override comunica la condición. Mantén defaults realistas y actualiza la factory cuando cambia una regla obligatoria. No uses aleatoriedad no reproducible para todo; identificadores secuenciales o UUID controlados suelen bastar.

const order = orderBuilder()
.withStatus("paid")
.withItem({ priceCents: 5000, quantity: 2 })
.withCoupon({ percent: 10 })
.build()

Un builder ayuda cuando existen estados válidos con muchas relaciones. Cada método debe representar lenguaje del dominio. Evita builders que permitan combinaciones imposibles solo para que el test compile.

Una fixture también administra recursos:

import { test as base } from "vitest"
export const test = base.extend<{
project: { id: string; name: string }
}>({
project: async ({}, use) => {
const project = await db.project.create({
data: { name: "Proyecto de prueba" }
})
await use(project)
await db.project.delete({ where: { id: project.id } })
}
})

El código antes de use prepara; el posterior limpia. El alcance por test ofrece mayor aislamiento. Compartir por archivo o worker reduce costo, pero exige que los tests no modifiquen estado común de manera incompatible.

EstrategiaVentajaRiesgo
transacción + rollbackrápida y limpiano funciona si la app usa otras conexiones
truncar tablasestado conocidocostoso y sensible a FK/paralelismo
IDs únicospermite paraleloacumula datos si no hay limpieza
schema por workerbuen aislamientoconfiguración y migraciones adicionales
contenedor por suitemotor real reproducibletiempo de arranque y Docker

No uses una base compartida sin nombres únicos y cleanup. Valida que la URL sea de test antes de ejecutar operaciones destructivas.

it("vence al terminar el día en la zona del negocio", () => {
vi.useFakeTimers()
vi.setSystemTime(new Date("2026-08-28T23:59:59-05:00"))
expect(isCouponActive(coupon)).toBe(true)
vi.advanceTimersByTime(1000)
expect(isCouponActive(coupon)).toBe(false)
})

Restaura timers después de cada caso. Para lógica crítica de zonas horarias, añade ejemplos en cambios de día, mes y horario de verano cuando la región lo utilice. Un fake timer no reproduce todos los detalles del runtime.

Un snapshot guarda una salida y falla cuando cambia. Es útil para estructuras estables, serializadores, AST, emails o mensajes largos donde el diff es la señal principal.

expect(renderInvoice(invoice)).toMatchInlineSnapshot(`
"Invoice INV-42
Total: $50.00
Status: paid"
`)

No es apropiado para un árbol enorme de UI que cambia por detalles irrelevantes. Un snapshot grande falla mucho y rara vez explica qué contrato se rompió.

  • pequeño y nombrado;
  • salida determinista;
  • sin timestamps, IDs ni orden aleatorio;
  • diff entendible;
  • revisado como código.
  • se actualiza con -u sin leer;
  • contiene toda la página;
  • reemplaza aserciones de permisos o cálculos;
  • cambia por formato no relevante;
  • incluye secretos o datos personales.

Para un compilador, importador o transformador, conserva entradas y salidas en archivos:

fixtures/
├── invoice-valid/input.csv
├── invoice-valid/expected.json
├── invoice-invalid/input.csv
└── invoice-invalid/error.txt

El archivo facilita casos reales y diffs. Documenta cómo regenerarlo y revisa cambios. Si el oráculo se genera con el mismo código bajo prueba, solo confirma su comportamiento actual.

  • seed global que cada test muta;
  • fixture con datos de producción sin anonimizar;
  • factory que consulta red o reloj sin control;
  • cleanup que oculta el fallo original;
  • beforeEach enorme con estado no usado;
  • snapshots actualizados en masa para “poner verde” CI.
  • el dato diferente está visible en el test;
  • defaults cumplen reglas reales;
  • IDs, reloj y random son reproducibles;
  • cada recurso tiene alcance y cleanup;
  • paralelismo no comparte estado mutable;
  • snapshots son pequeños y revisables;
  • ningún fixture contiene secretos o datos reales.