Hoy en día no se pone en duda que la calidad del software y su maleabilidad van directamente ligadas a la capacidad de comprobar si éste sigue funcionando tras un cambio, un refactor o una nueva funcionalidad. Dedicamos tiempo y esfuerzo a desarrollar las pruebas automáticas de nuestras aplicaciones con el objetivo de no caer en una montaña de deuda técnica y poder seguir adaptándonos a los cambios a los que hay que adaptar el software día tras día.
Una de las tecnologías que más nos puede ayudar a la hora de comprobar que nuestro software funciona es sin lugar a dudas Test containers. Test containers es un framework open-source que nos permite desplegar dependencias de infrastructura de nuestra aplicación al lanzar los tests, de manera que podemos probar la aplicación contra la dependencia de infrastructura real.
Podemos desplegar desde bases de datos relacionales, no relacionales, brokers de mensajería así como cualquier componente desplegable con Docker. Sólo necesitamos que al lanzar nuestros tests éstos puedan comunicarse con un daemon de Docker y desplegar los test containers que hayamos configurado.
¿Qué problema resuelven los test containers?
Al permitirnos desplegar de manera automática dependencias de infrastructura de nuestra aplicación, test containers nos facilita escribir tests de integración que se aseguren de que los componentes de nuestra aplicación funcionan correctamente sobre la infrastructura concreta. Sin test containers, tendríamos que manejar la instancia de esta dependencia de infrastructura externamente. Usar una instancia externa compartida introduce problemas y limitaciones a la hora de inicializar esquemas, datos de prueba, ejecución concurrente de los tests por parte de varios desarrolladores, …
Con test containers nuestros tests siempre arrancan con una instancia nueva dedicada solo para esa ejecución de tests y no tenemos que preocuparnos por estas situaciones.
Test containers en Spring Boot
Vamos a ponernos manos a la obra para probar la capa de persistencia de un microservicio con test containers. El microservicio va a ser un sencillo API REST de ubicaciones que hemos usado en otros artículos como Midiendo el rendimiento de un servicio Java 21 con virtual threads y que podemos encontrar en el repositorio de Github Spring Boot Location API. Para poder arrancar el servicio es necesario contar con Docker instalado en nuestro sistema y un JDK Java 21 o superior.
La idea de este ejercicio es probar que la aplicación es capaz de usar como motor de persistencia tanto PostgreSQL como MongoDB. Empecemos con la parte de PostgreSQL.
Añadiendo los tests de infrastructura PostgreSQL
Con estas dependencias en el fichero build.gradle
estamos añadiendo el módulo de Spring Boot para hacer uso de test containers y también el módulo de test containers para soportar PostgreSQL.
Como siguiente paso, en nuestro código de test tendremos que decirle qué imagen docker usamos para nuestro test container e iniciarlo como un bean de Spring Boot.
Gracias a la anotación @ServiceConnection
Spring no va a necesitar que configuremos en la configuración de la aplicación la conexión a nuestro contenedor docker,
sino que él mismo sabrá como conectarse al test container y gestionará la cadena de conexión. A partir de ese momento ya es posible escribir tests en nuestra aplicación que usen el PostgreSQL desplegado por el test container.
Usaremos el patrón de diseño Template method test pattern para poder reutilizar nuestros tests para cada uno de los motores de persistencia.
La clase abstracta PersistenceIntegrationTest
será la que usaremos como una plantilla con los tests de la que extenderán otras clases concretas.
La clase PostgresIntegrationTest
heredará los tests de PersistenceIntegrationTest
y activará el perfil de Spring postgres
. Ese perfil,
será el que se utilizará en la aplicación para activar la persistencia con PostgreSQL e inicializará el bean que implemente la interfaz de repositorio LocationRepository
.
Añadiendo los tests de infrastructura MongoDB
Como primer paso tendremos que añadir el soporte al test container de mongo.
Y el bean necesario para arrancar el test container de mongo.
Como paso final, crearemos otra clase hija de PersistenceIntegrationTest
para ejecutar los tests contra este motor de base de datos.
En este punto ya contaríamos con un marco de trabajo para que en nuestro entorno de CI se garantice que nuestra aplicación funciona contra ambos motores de bases de datos y, no solo eso, sino exactamente contra la versión del motor de base de datos usado en nuestros entornos productivos.
Test containers y Github Actions
Como broche final, quería hacer una mención especial a Github Actions. Con Github podemos utilizar Github Actions como nuestro entorno de CI/CD. En el repositorio de ejemplo podemos comprobar como Github Actions construye el artefacto del servicio con Gradle, ejecutando los tests con test containers en el proceso.