Writing database tests that mimic real-world scenarios is crucial β but in-memory databases like H2 often fall short in simulating true production environments. This is where Testcontainers comes in. Testcontainers is a Java library that provides lightweight, throwaway instances of common databases (like PostgreSQL, MySQL) inside Docker containers for your tests.
In this post, we’ll explore Spring Boot Testcontainers Database Testing, showing you how to run real PostgreSQL containers for integration tests that are fast, repeatable, and production-representative.

π¦ Project Setup
π§ Prerequisites:
- Java 17+ (recommended)
- Docker installed and running
- Spring Boot 3.x
π§Ύ Maven Dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<dependencies> <!-- Spring Data JPA + PostgreSQL --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Spring Boot Test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Testcontainers for PostgreSQL --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.19.1</version> <scope>test</scope> </dependency> </dependencies> |
π Project Structure
1 2 3 4 5 6 7 8 9 10 |
com.kscodes.springboot βββ model β βββ Customer.java βββ repository β βββ CustomerRepository.java βββ test βββ CustomerRepositoryTest.java |
π§± Entity: Customer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.kscodes.springboot.model; import jakarta.persistence.*; @Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; // Getters and Setters } |
π Repository Interface
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.kscodes.springboot.repository; import com.kscodes.springboot.model.Customer; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface CustomerRepository extends JpaRepository<Customer, Long> { Optional<Customer> findByEmail(String email); } |
π§ͺ Integration Test with Testcontainers
Hereβs how to create a reusable PostgreSQL container for all your Spring Boot integration tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package com.kscodes.springboot; import com.kscodes.springboot.model.Customer; import com.kscodes.springboot.repository.CustomerRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest @Testcontainers public class CustomerRepositoryTest { @Container static PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:16.1") .withDatabaseName("testdb") .withUsername("test") .withPassword("test"); @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgresContainer::getJdbcUrl); registry.add("spring.datasource.username", postgresContainer::getUsername); registry.add("spring.datasource.password", postgresContainer::getPassword); } @Autowired private CustomerRepository customerRepository; @BeforeEach void setup() { Customer customer = new Customer(); customer.setName("Alice"); customer.setEmail("alice@example.com"); customerRepository.save(customer); } @Test void testFindByEmail() { Optional<Customer> result = customerRepository.findByEmail("alice@example.com"); assertThat(result).isPresent(); assertThat(result.get().getName()).isEqualTo("Alice"); } } |
βοΈ How It Works
@Testcontainers
: Activates Testcontainers JUnit integration@Container
: Lifecycle-managed PostgreSQL container@DynamicPropertySource
: Dynamically injects DB properties into SpringβsEnvironment
@DataJpaTest
: Loads only the JPA-related components for fast test execution
β Benefits of Spring Boot Testcontainers Database Testing
Benefit | Description |
---|---|
π§ͺ Real Environment | Uses PostgreSQL/MySQL as in production |
π Fast & Isolated | Containers are ephemeral and disposable |
π Repeatable | Same test setup on every run |
π CI/CD Friendly | Works on local or cloud build agents with Docker |
π§ Tips for Best Practices
- Prefer one container per test class to improve performance.
- Use
@TestConfiguration
if you need to add beans during testing. - Use
@SpringBootTest
instead of@DataJpaTest
if testing with services/controllers. - Clean up DB state between tests using
@BeforeEach
or@DirtiesContext
.
π Summary
In this guide, you learned how to implement Spring Boot Testcontainers Database Testing using PostgreSQL. We covered:
- Setting up Dockerized PostgreSQL for tests
- Writing dynamic Spring Boot configurations
- Running
@DataJpaTest
with Testcontainers - Best practices for production-similar test environments
This approach gives you confidence that your application will behave correctly when deployed to real databases.