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.