Integration Testing Java with Docker: From A to Z
\n\nIntegration testing is a key development stage that allows you to verify the interaction of application components with external systems: databases, message queues, REST services. The traditional approach using H2 or embedded servers often leads to discrepancies between the test and production environments. The solution is Docker. In this article, we will break down how to set up integration testing for Java applications with Docker using the Testcontainers library and Docker Compose. You will learn how to write tests that work with real containers and how to integrate them into CI/CD.
\n\nWhy Docker for Integration Tests?
\nDocker allows you to spin up isolated containers with exact versions of dependencies (PostgreSQL, Redis, Kafka) right before tests and destroy them afterwards. This guarantees:
\n- \n
- Production parity — you test with a real database, not an emulation. \n
- Isolation — each test run starts from a clean state. \n
- Parallel execution — multiple test classes can use different containers. \n
- Reproducibility — tests work the same way on a local machine and in CI. \n
Project Setup with Testcontainers
\nTestcontainers is a Java library that manages the lifecycle of Docker containers. Let's add the dependency to pom.xml (Maven):
<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>\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-test</artifactId>\n <scope>test</scope>\n</dependency>\n\nFor Gradle, add to build.gradle:
testImplementation 'org.testcontainers:testcontainers:1.19.3'\ntestImplementation 'org.testcontainers:postgresql:1.19.3'\n\nExample: Testing a Repository with PostgreSQL
\nLet's consider a simple service for working with users. We'll create a test that spins up a PostgreSQL container and checks data persistence.
\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.DynamicPropertyRegistry;\nimport org.springframework.test.context.DynamicPropertySource;\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\n@SpringBootTest\nclass UserRepositoryTest {\n\n @Container\n static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")\n .withDatabaseName("testdb")\n .withUsername("test")\n .withPassword("test");\n\n @DynamicPropertySource\n static void configureProperties(DynamicPropertyRegistry registry) {\n registry.add("spring.datasource.url", postgres::getJdbcUrl);\n registry.add("spring.datasource.username", postgres::getUsername);\n registry.add("spring.datasource.password", postgres::getPassword);\n }\n\n @Autowired\n private UserRepository userRepository;\n\n @Test\n void shouldSaveAndFindUser() {\n User user = new User("john_doe", "john@example