BookinglyTech News
Software

Testcontainers y el fin de la base de datos en memoria en las pruebas de integración

Mockear la capa de persistencia prueba tus suposiciones sobre la base de datos, no la base de datos. Levantar el motor real en un contenedor desechable cambia lo que el CI detecta.

2 min de lecturaDev.to0 vistas

El test contra una base de datos en memoria que pasa en verde mientras el despliegue revienta es un clásico. La alternativa que defienden quienes ya la usan: arrancar el motor real, en un contenedor desechable, desde el propio código de test. Eso es Testcontainers, con implementaciones para Node.js, Go, Python, Java, .NET y unos cuantos más.

El patrón antiguo —H2 o SQLite en los tests, PostgreSQL o MySQL en producción— genera una confianza que no está respaldada por nada. Mockear la capa de persistencia no comprueba cómo se comporta la base de datos: comprueba lo que el desarrollador cree saber sobre ella. Y las diferencias aparecen justo donde duele.

Dónde se rompe el sustituto

Los operadores nativos de JSON y JSONB no se comportan igual entre dialectos. El bloqueo a nivel de fila (SELECT ... FOR UPDATE), los niveles de aislamiento de transacción y los deadlocks no se reproducen de forma fiable sobre un motor en memoria. Y las extensiones de PostgreSQL —pgvector, PostGIS, índices de búsqueda de texto completo— sencillamente no existen en H2 ni en SQLite. Ningún test que pase contra el sustituto dice nada de esos casos.

Testcontainers invierte el planteamiento: el test arranca un contenedor Docker real, con la versión concreta del motor, ejecuta las migraciones contra él y lo destruye al terminar. En la práctica es un beforeAll que levanta el contenedor y un afterAll que lo para.

Qué se gana y qué cuesta

Aislamiento. Cada ejecución provisiona su propio contenedor en un puerto aleatorio del host, así que desaparecen las dependencias de orden entre tests y el estado sucio que arrastra una base de staging compartida.

No solo SQL. En un mismo archivo de test se pueden orquestar varios servicios: Redis para validar la invalidación de caché, Kafka o RabbitMQ para consumidores asíncronos, o LocalStack para probar subidas a S3 en local.

Determinismo. Como la etiqueta de imagen se fija en el propio código (por ejemplo postgres:16-alpine), el portátil del desarrollador y el runner de GitHub Actions o GitLab CI ejecutan exactamente el mismo motor.

El precio es el tiempo. Levantar contenedores en CI no es gratis, y hay tres medidas habituales para que no se note: activar la reutilización de contenedores en desarrollo local para no pagar el arranque en cada cambio de código, tirar de imágenes mínimas tipo alpine o slim, y montar el directorio de datos del motor sobre tmpfs para saltarse el cuello de botella del disco durante la ejecución.

Nada de esto es un anuncio ni una versión nueva: la técnica lleva años madurando y sigue chocando con la factura de minutos de CI de quien tiene suites grandes. Pero el argumento de fondo aguanta. Si el motor no es el mismo que corre en producción, lo que se está probando es otra cosa.