{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiax4h4npjoh4bdsq4xnv5z566rddoip2zdndlocpmu7cvo5iupkiq",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mommyd643lc2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreichjrlnmgmb3vwxmfigopnnveomv66exrjrjzqabcsud2rwkqcbrm"
},
"mimeType": "image/webp",
"size": 72780
},
"path": "/nitish_95c3a169c005f1d4fe/spring-boot-32-testcontainers-reliable-integration-testing-with-real-dependencies-5bfo",
"publishedAt": "2026-06-19T05:21:27.000Z",
"site": "https://dev.to",
"tags": [
"java",
"springboot",
"programming",
"tutorial",
"https://gumroad.com",
"@Testcontainers",
"@SpringBootTest",
"@Container",
"@Autowired",
"@Test",
"@DynamicPropertySource"
],
"textContent": "# Spring Boot 3.2 + Testcontainers: Reliable Integration Testing with Real Dependencies\n\nWithout Testcontainers, your integration tests might pass locally with H2 but fail in CI when connecting to a mismatched PostgreSQL version. Production database migrations succeed in development but break when applied to a different database engine.\n\n## Prerequisites\n\n * Java 17 or later\n * Spring Boot 3.2.x\n * Docker Engine 24.0+ with Docker Compose\n * JUnit Jupiter 5.9+\n * Testcontainers 1.19.3\n\n\n\n## Configuring Testcontainers for Database Testing\n\nTestcontainers provides disposable database instances for integration tests. Add these dependencies to your `pom.xml`:\n\n\n\n <parent>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-parent</artifactId>\n <version>3.2.4</version>\n </parent>\n\n <dependencies>\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-test</artifactId>\n <scope>test</scope>\n </dependency>\n <dependency>\n <groupId>org.testcontainers</groupId>\n <artifactId>junit-jupiter</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 </dependencies>\n\n\nDisable the default datasource in test configuration:\n\n\n\n # src/test/resources/application.properties\n spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver\n spring.datasource.url=jdbc:tc:postgresql:15-alpine:///test?TC_DAEMON=true\n spring.flyway.enabled=false\n\n\n## Writing a Containerized Integration Test\n\nThis test verifies database schema initialization using a real PostgreSQL instance. The container starts before tests and destroys itself afterward:\n\n\n\n package com.example.orderservice;\n\n import org.junit.jupiter.api.Test;\n import org.springframework.beans.factory.annotation.Autowired;\n import org.springframework.boot.test.context.SpringBootTest;\n import org.springframework.jdbc.core.JdbcTemplate;\n import org.testcontainers.containers.PostgreSQLContainer;\n import org.testcontainers.junit.jupiter.Container;\n import org.testcontainers.junit.jupiter.Testcontainers;\n\n import static org.assertj.core.api.Assertions.assertThat;\n\n @Testcontainers\n @SpringBootTest\n class OrderRepositoryIntegrationTest {\n\n @Container\n static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:15-alpine\")\n .withDatabaseName(\"test\")\n .withUsername(\"test\")\n .withPassword(\"test\");\n\n @Autowired\n private JdbcTemplate jdbcTemplate;\n\n @Test\n void should_connect_to_live_database() {\n Integer result = jdbcTemplate.queryForObject(\"SELECT 1\", Integer.class);\n assertThat(result).isEqualTo(1);\n }\n }\n\n\nRun the test with Docker Desktop active. Testcontainers will download the PostgreSQL image on first execution and reuse it for subsequent runs.\n\n## Managing Container Lifecycles\n\nDefine a base test class to share containers across multiple test classes:\n\n\n\n package com.example.shared;\n\n import org.springframework.test.context.DynamicPropertyRegistry;\n import org.springframework.test.context.DynamicPropertySource;\n import org.testcontainers.containers.PostgreSQLContainer;\n import org.testcontainers.junit.jupiter.Container;\n import org.testcontainers.junit.jupiter.Testcontainers;\n\n @Testcontainers\n public abstract class BaseIntegrationTest {\n\n @Container\n static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:15-alpine\")\n .withReuse(true);\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\n\n## Common Mistakes\n\n**Mistake 1: Non-static container field causing multiple instances**\n\n\n\n @Container // Missing static modifier\n PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();\n\n\n\n @Container\n static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();\n\n\nTestcontainers requires static fields for class-level containers. Non-static fields create new containers per test method.\n\n**Mistake 2: Hardcoded JDBC URL in test properties**\n\n\n\n spring.datasource.url=jdbc:postgresql://localhost:5432/test\n\n\n\n @DynamicPropertySource\n static void registerProperties(DynamicPropertyRegistry registry) {\n registry.add(\"spring.datasource.url\", postgres::getJdbcUrl);\n }\n\n\nHardcoded URLs bypass container networking. Use `@DynamicPropertySource` to inject runtime connection details.\n\n**Mistake 3: Missing container reuse configuration**\n\n\n\n new PostgreSQLContainer<>(\"postgres:15-alpine\");\n\n\n\n new PostgreSQLContainer<>(\"postgres:15-alpine\")\n .withReuse(true);\n\n\nWithout reuse, Testcontainers destroys containers after each test class. Enable reuse in `.testcontainers.properties` and chain `withReuse(true)`.\n\n## Summary\n\n * Replace in-memory databases with Testcontainers-managed PostgreSQL instances using `@Testcontainers` and `@Container`\n * Inject dynamic connection properties using `@DynamicPropertySource` registry\n * Enable container reuse with `.withReuse(true)` and `testcontainers.reuse.enable=true` in `.testcontainers.properties`\n\n\n\n_The author publishes Spring Boot starter templates at https://gumroad.com_",
"title": "Spring Boot 3.2 + Testcontainers: Reliable Integration Testing with Real Dependencies"
}