Integration Testing Java with Docker: Advanced Level
\nIntegration testing is a key stage in the development of modern Java applications, especially in a microservices architecture. Using Docker allows you to isolate test environments, ensuring reproducibility and independence from external services. In this article, we will look at advanced techniques for integration testing Java applications with Docker, including working with the Testcontainers library, configuring custom containers, testing asynchronous scenarios, and integration into CI/CD pipelines. The material is intended for developers already familiar with the basics of JUnit and Docker.
\n\nWhy Docker is Critical for Java Integration Testing
\nTraditional integration testing often faces problems: unstable test databases, port conflicts, manual environment setup. Docker solves these problems by providing lightweight, ephemeral containers. Each test can run its own database, message broker, or cache, and automatically destroy them upon completion. This makes tests:
\n- \n
- Isolated: each test scenario runs in a clean environment. \n
- Reproducible: consistent behavior on a local machine and in CI. \n
- Parallel: containers do not conflict on ports thanks to dynamic mapping. \n
For Java, the most popular library is Testcontainers, which provides a simple API for managing containers from tests.
\n\nSetting up Testcontainers: From Simple to Complex
\nLet's start with a basic example — testing the DAO layer with PostgreSQL. Add the dependency to 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>\nNow let's create a test that uses a PostgreSQL container:
\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 // Get the dynamic URL from the container\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 automatically starts the container before tests and stops it after. For an advanced level, use custom containers (for example, with pre-loaded