Pruebas de integración Java con Docker: nivel avanzado
\nLas pruebas de integración son una etapa clave en el desarrollo de aplicaciones Java modernas, especialmente en la arquitectura de microservicios. El uso de Docker permite aislar los entornos de prueba, garantizando la reproducibilidad y la independencia de los servicios externos. En este artículo, exploraremos técnicas avanzadas de pruebas de integración de aplicaciones Java con Docker, incluyendo el trabajo con la biblioteca Testcontainers, la configuración de contenedores personalizados, las pruebas de escenarios asíncronos y la integración en pipelines CI/CD. El material está dirigido a desarrolladores que ya están familiarizados con los fundamentos de JUnit y Docker.
\n\n¿Por qué Docker es crítico para las pruebas de integración Java?
\nLas pruebas de integración tradicionales a menudo enfrentan problemas: bases de datos de prueba inestables, conflictos de puertos, configuración manual del entorno. Docker resuelve estos problemas proporcionando contenedores ligeros y efímeros. Cada prueba puede ejecutar su propia base de datos, broker de mensajes o caché, y al finalizar, destruirlos automáticamente. Esto hace que las pruebas sean:
\n- \n
- Aisladas: cada escenario de prueba se ejecuta en un entorno limpio. \n
- Reproducibles: mismo comportamiento en la máquina local y en CI. \n
- Paralelas: los contenedores no entran en conflicto por los puertos gracias al mapeo dinámico. \n
Para Java, la biblioteca más popular es Testcontainers, que proporciona una API simple para gestionar contenedores desde las pruebas.
\n\nConfiguración de Testcontainers: de lo simple a lo complejo
\nComencemos con un ejemplo básico: probar la capa DAO con PostgreSQL. Agregue la dependencia en pom.xml:
<dependency>\n <groupId>org.testcontainers</groupId>\n <artifactId>testcontainers</artifactId>\n <version>1.19.3</version>\n <scope>test</scope>\n</dependency>\n<dependency>\n <groupId>org.testcontainers</groupId>\n <artifactId>postgresql</artifactId>\n <version>1.19.3</version>\n <scope>test</scope>\n</dependency>\nAhora creemos una prueba que utiliza un contenedor PostgreSQL:
\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@Testcontainers\npublic class UserRepositoryIntegrationTest {\n\n @Container\n private PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")\n .withDatabaseName("testdb")\n .withUsername("test")\n .withPassword("test");\n\n @Test\n void shouldSaveAndFindUser() throws Exception {\n // Obtenemos la URL dinámica del contenedor\n String jdbcUrl = postgres.getJdbcUrl();\n String username = postgres.getUsername();\n String password = postgres.getPassword();\n\n try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {\n conn.createStatement().execute("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(255))");\n conn.createStatement().execute("INSERT INTO users (name) VALUES ('Alice')");\n ResultSet rs = conn.createStatement().executeQuery("SELECT name FROM users WHERE id = 1");\n rs.next();\n assertEquals("Alice", rs.getString("name"));\n }\n }\n}\nTestcontainers inicia automáticamente el contenedor antes de las pruebas y lo detiene después. Para un nivel avanzado, use contenedores personalizados (por ejemplo, con datos precargados