CodeGym /Cursos /Módulo 5. Spring /Lección 140: Uso de Testcontainers para probar con una ba...

Lección 140: Uso de Testcontainers para probar con una base de datos real

Módulo 5. Spring
Nivel 9 , Lección 9
Disponible

Probar usando una base de datos real es vital en aplicaciones complejas. Imagina que desarrollas una aplicación y en producción usas PostgreSQL. Escribes pruebas usando H2 y todo funciona de maravilla... hasta el primer encuentro con el mundo real, donde el esquema de datos se rompe de formas inesperadas. Ahí es donde entra en juego Testcontainers.

Testcontainers —es una librería Java que permite levantar contenedores Docker aislados para usarlos en tests. Te da la posibilidad de levantar una base de datos real (PostgreSQL, MySQL, MongoDB y otras) en un contenedor, probar la app y destruir el contenedor automáticamente al terminar la prueba. Nada de "regalitos" como restos de datos de prueba en tu máquina.


Ventajas de Testcontainers

  • Comportamiento real: usar la misma base de datos que en producción reduce la probabilidad de sorpresas.
  • El mismo entorno en todas partes: los contenedores garantizan el mismo entorno para los tests en tu máquina local, CI/CD e incluso en tu nevera, si soporta Docker.
  • Limpieza: después de la prueba el contenedor se destruye, dejando tu sistema intacto.
  • Soporte para muchas BD: PostgreSQL, MySQL, MariaDB, MongoDB, Cassandra e incluso Kafka.

Instalación de Testcontainers

Dependencias Maven/Gradle

Para empezar añadimos Testcontainers al proyecto. Usaremos PostgreSQL como ejemplo de base de datos.

Maven:


<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

Gradle:


testImplementation("org.testcontainers:postgresql:1.19.0")
Importante:
Para que Testcontainers funcione, Docker debe estar instalado y en ejecución en tu máquina.

Configuración básica de Testcontainers con PostgreSQL

Empecemos creando una prueba básica que use un contenedor PostgreSQL.

Paso 1: Preparar el contenedor de prueba

Crearemos una clase de tests donde usemos el contenedor. Testcontainers permite configurar contenedores para PostgreSQL de la siguiente manera:


import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;

public class PostgresContainerTest {

    @Test
    void testPostgresContainer() {
        try (PostgreSQLContainer
    postgres = new PostgreSQLContainer<>("postgres:15")) {
            // Iniciamos el contenedor
            postgres.start();

            // Imprimimos los datos del contenedor para comprobar
            System.out.println("Postgres URL: " + postgres.getJdbcUrl());
            System.out.println("Username: " + postgres.getUsername());
            System.out.println("Password: " + postgres.getPassword());

            // Aquí puedes conectarte a la BD vía JDBC y ejecutar acciones de prueba
        }
    }
}

Esta prueba levanta un contenedor con Postgres versión 15. Tras llamar a start() el contenedor arranca y Testcontainers localiza automáticamente un puerto disponible para la base. Al terminar el bloque try el contenedor se detiene.

Paso 2: Integración con Spring Boot

Haremos que Spring Boot use este contenedor en las pruebas. La configuración se hace con @DynamicPropertySource.


import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
class DatabaseIntegrationTest {

    @Container
    private static final PostgreSQLContainer
    POSTGRES = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void overrideProperties(org.springframework.test.context.DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
    }

    @Test
    void contextLoads() {
        // Tu prueba aquí
    }
}

¿Qué ocurre aquí?

  1. La anotación @Testcontainers: indica que estamos usando Testcontainers.
  2. La anotación @Container: gestiona automáticamente el ciclo de vida del contenedor (arranque/detención).
  3. @DynamicPropertySource: sobrescribe las propiedades de spring.datasource para que Spring use los parámetros del contenedor.

Escribir una prueba para el repositorio

Añadamos una prueba para el repositorio donde comprobemos guardar una entidad en la BD.

Entidad User:


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    // Getters y setters
}

Repositorio:


import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Prueba:


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit.jupiter.Testcontainers;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@Testcontainers
class UserRepositoryTest {

    @Container
    private static final PostgreSQLContainer
    POSTGRES = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void overrideProperties(org.springframework.test.context.DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void testSaveUser() {
        User user = new User();
        user.setName("John Doe");

        User savedUser = userRepository.save(user);
        assertThat(savedUser.getId()).isNotNull();
        assertThat(savedUser.getName()).isEqualTo("John Doe");
    }
}

Esta prueba arranca el contenedor de la BD, lo conecta a Spring Boot y permite probar el guardado de la entidad sin tener que levantar Postgres manualmente.


Particularidades y errores

  • Docker debe estar instalado y en ejecución: si Docker no funciona, tus pruebas simplemente no se ejecutarán.
  • ¿El puerto 5432 ya está en uso? Testcontainers usa automáticamente un puerto aleatorio para el contenedor, así que se evitan conflictos.
  • ¿Inicialización lenta? el primer arranque puede tardar más por la descarga de la imagen desde Docker Hub.

Aplicación práctica

  1. Entorno de pruebas real: puedes estar seguro de que tu aplicación funcionará igual en producción que en las pruebas.
  2. Automatización CI/CD: incluyendo Testcontainers en los pipelines (por ejemplo, GitHub Actions o GitLab CI) harás las pruebas de integración más fiables.
  3. Probar diferentes configuraciones dinámicamente: cambia parámetros del contenedor (por ejemplo, la versión de Postgres) y verifica que todo sigue funcionando.

Testcontainers es una herramienta potente para pruebas de integración con bases de datos reales, que hace tu proceso de testing más confiable y cercano a la explotación real. Aprende a usarlo, y no olvides que los contenedores hay que apagarlos después de las pruebas... ¡Por suerte Testcontainers lo hace automáticamente! 😉

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION