Cookies

Esta web utiliza cookies para obtener datos estadísticos de la navegación de sus usuarios. Si continúas navegando consideramos que aceptas su uso.

Tests de integración de bases de datos con Test containers y Spring Boot

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.

Test containers

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

ext {
    springBootVersion = '3.2.8'
    springDependencyManagementVersion = '1.1.4'
}

plugins {
    id 'java'
    id 'org.springframework.boot' version "${springBootVersion}"
    id 'io.spring.dependency-management' version "${springDependencyManagementVersion}"
}

dependencies {
    // Otras dependencias...


    testImplementation 'org.springframework.boot:spring-boot-testcontainers'
    testImplementation 'org.testcontainers: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.

@TestConfiguration(proxyBeanMethods = false)
public class LocationApiTestContainersConfiguration {

    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresqlContainer() {

        return new PostgreSQLContainer<>(DockerImageName.parse("postgres:16-alpine"))
            .withUrlParam("binaryTransfer", "true")
            .withUrlParam("rewriteBatchedInserts", "true");
    }
// ...


}

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.

public abstract class PersistenceIntegrationTest {

    @Autowired
    LocationRepository locationRepository;

    @Test
    void testLocationDoesNotExist() {
        LocationId locationId = new LocationId(1);

        Assertions.assertThatThrownBy(() -> locationRepository.get(locationId))
                .isInstanceOf(EntityNotFoundException.class);
    }

    // ...

}

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.

@Import(LocationApiTestContainersConfiguration.class)
@ActiveProfiles("postgres")
@SpringBootTest(classes = {TestLocationApiApplication.class})
@Rollback
public class PostgresIntegrationTest extends PersistenceIntegrationTest {
}

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.

dependencies {

    // Otras dependencias...


    testImplementation 'org.testcontainers:mongodb'
}

Y el bean necesario para arrancar el test container de mongo.

@TestConfiguration(proxyBeanMethods = false)
public class LocationApiTestContainersConfiguration {
    // ...


    @Profile("mongo")
    @Bean
    @ServiceConnection
    MongoDBContainer mongoContainer() {
        return new MongoDBContainer("mongo:6.0.16");
    }
}

Como paso final, crearemos otra clase hija de PersistenceIntegrationTest para ejecutar los tests contra este motor de base de datos.

@Import(LocationApiTestContainersConfiguration.class)
@ActiveProfiles("mongo")
@SpringBootTest(classes = {TestLocationApiApplication.class})
public class MongoIntegrationTest extends PersistenceIntegrationTest {
}

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.