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.