====== 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**. ===== 1. Qué se prueba ===== 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 | ===== 2. Testcontainers ===== **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. ===== 3. La prueba ===== 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 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. ===== 4. Datos de prueba ===== 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. ===== 5. Con Spring Boot ===== 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.