12 - Pruebas de integración: Testcontainers

Las unitarias (tema 05) sustituyen las dependencias por dobles: comprueban la lógica de cada pieza aislada, pero no la infraestructura. Un mock no ejecuta SQL, no tiene un esquema y no aplica restricciones. Cuando lo que hay que comprobar es la colaboración con una dependencia real — el caso típico: el DAO contra la base de datos — la pieza es la prueba de integración.

La prueba de integración del DAO ejecuta el código real contra un motor real:

Se comprueba No se comprueba
El SQL correcto contra el motor real Reglas de negocio (pruebas de dominio)
Restricciones: claves, NOT NULL, únicas La interfaz y los flujos de usuario (E2E, tema 10)
El mapeo de resultados a modelo El rendimiento
Unitarias (Mockito) Integración (BD real)
Milisegundos Segundos
Muchas: la base de la pirámide Pocas: lo que las unitarias no pueden ver

Testcontainers levanta en Docker las dependencias reales de la aplicación durante las pruebas: el contenedor se crea al empezar la suite, se usa y se destruye al terminar. No hay una base de datos compartida que ensuciar: cada ejecución parte de cero.

La clave es usar la misma versión de MariaDB que producción y apuntar el código bajo prueba a las credenciales del contenedor:

@Testcontainers
class BookDaoIntegrationTest {

    @Container
    static MariaDBContainer<?> mariadb = new MariaDBContainer<>("mariadb:12.3");

    static BookDao bookDao;

    @BeforeAll
    static void startDatabase() {
        // 1. crea el esquema: las migraciones del proyecto
        Flyway.configure()
                .dataSource(mariadb.getJdbcUrl(),
                            mariadb.getUsername(),
                            mariadb.getPassword())
                .load()
                .migrate();

        // 2. código bajo prueba, conectado al contenedor
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(mariadb.getJdbcUrl());
        dataSource.setUsername(mariadb.getUsername());
        dataSource.setPassword(mariadb.getPassword());
        bookDao = new JdbcBookDao(dataSource);
    }
}

Elemento Función
@Testcontainers Activa la gestión de contenedores en la suite
@Container Levanta y destruye el contenedor con la suite
getJdbcUrl() y similares Credenciales del contenedor, sin valores fijados a mano
Flyway…migrate() Aplica las migraciones: esquema idéntico al del proyecto

El DAO y la migración del esquema se explican en el módulo de Bases de datos; aquí lo nuevo es el entorno real y efímero donde se comprueba.

Con el contenedor y el esquema listos, la prueba es AAA convencional:

@Test
void savesAndFindsBook() {
    // Arrange
    Book book = new Book(0, "Clean Code", new BigDecimal("34.50"), 1);

    // Act
    Book saved = bookDao.save(book);

    // Assert
    Optional<Book> found = bookDao.findById(saved.id());

    assertTrue(found.isPresent());
    assertEquals("Clean Code", found.get().title());
}

Aquí no hay nada simulado: el id lo genera el AUTO_INCREMENT de la tabla, la clave foránea comprueba que la editorial existe y el mapeo usa el esquema real. Si el SQL o el mapeo están mal, la prueba lo detecta.

Cada prueba prepara los datos que consulta y no asume lo que otras dejaron (pruebas aisladas, tema 04). Lo más sencillo: cada una inserta lo que necesita y trabaja con los id que la propia base devuelve, como el saved.id() del ejemplo.

Cuando la aplicación es Spring Boot (módulo de Bases de datos), la anotación @ServiceConnection sobre el @Container conecta la aplicación de prueba al contenedor sin configurar la DataSource a mano.

Testcontainers necesita Docker disponible donde se ejecutan las pruebas: en el equipo local y en el sistema de CI.
Son más lentas que las unitarias y no las sustituyen: la pirámide del tema 04 sigue mandando. Las unitarias llevan el peso; unas pocas de integración comprueban la fricción real con la infraestructura.
  • clase/daw/ed/2eval/integracion.txt
  • Última modificación: 2026/10/21 10:45
  • por cesguiro