Hace unos trimestres, nuestra suite de tests de integración estaba 100% verde para una migración que claramente no iba a llegar a producción sin tirar la base abajo por una hora. Los tests mockeaban el cliente de base de datos. La migración era real. Predeciblemente, el mock y la realidad divergieron.
El principio
Los tests de integración deben golpear el mismo motor que producción. Cualquier cosa menos y estás probando tus mocks, no tu código.
Suena obvio escrito así. En la práctica es difícil, porque las bases reales son lentas para levantar, más difíciles de resetear, y requieren pensar en aislamiento entre tests.
Qué cambiamos
- Cada worker de CI ahora tiene su propio Postgres efímero vía Testcontainers.
- Las migraciones corren como parte del setup de la suite — si falla la migración, falla la suite.
- Los fixtures de tests usan
RefreshDatabase(o el equivalente de Pest), así el schema es la fuente de verdad, no un grafo defactory()->mock(). - Agregamos un solo guardarraíl: cualquier test que mockee una
Connection,PDO, oDatabaseManagerrequiere aprobación explícita en code review.
El costo
Nuestra suite pasó de 38 segundos a 2 minutos 14. Lo consideramos dinero bien gastado — la siguiente migración que enviamos, la suite atrapó un constraint not null que habíamos pasado por alto en una tabla de 50M filas, y eso habría tirado el sitio por 90 minutos.
Qué seguimos mockeando
Servicios externos que no operamos: procesadores de pago, la API de facturación de Hacienda, endpoints de LLM de terceros. La línea son fronteras que no podemos correr localmente. Todo lo que está adentro, lo corremos de verdad.