Testcontainers and Spring Boot: Seamless Integration

Illustration for Testcontainers and Spring Boot: Seamless Integration
By Last updated:

dern Java applications often rely on Spring Boot for rapid development and Testcontainers for reliable testing. When combined, they provide a powerful toolkit for building production-ready systems while maintaining test quality. This tutorial explores how to seamlessly integrate Testcontainers with Spring Boot, ensuring your microservices, APIs, and database-driven applications remain stable under continuous delivery pipelines.


What is Testcontainers?

Testcontainers is a Java library that enables running real Docker containers for testing purposes. Instead of relying on mocks or embedded databases, developers can test against actual dependencies like PostgreSQL, MySQL, Kafka, RabbitMQ, or Redis.

Key benefits include:

  • Production-like environments during tests.
  • Disposable containers that ensure test isolation.
  • CI/CD compatibility with Jenkins, GitHub Actions, and GitLab CI.

Why Integrate Testcontainers with Spring Boot?

Spring Boot provides powerful dependency injection, auto-configuration, and test utilities. However, relying on in-memory databases or mocks can lead to discrepancies between test and production. Integrating Testcontainers solves this problem by ensuring your tests run against real-world systems.

Real-world importance:

  • Prevents bugs caused by environment mismatch.
  • Improves confidence in microservices architecture.
  • Simplifies integration and contract testing.
  • Enables repeatable tests across developer machines and CI/CD pipelines.

Setting Up Testcontainers in a Spring Boot Project

Step 1: Add Dependencies

For Maven:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.20.1</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.20.1</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

For Gradle:

testImplementation "org.testcontainers:junit-jupiter:1.20.1"
testImplementation "org.testcontainers:postgresql:1.20.1"
testImplementation "org.springframework.boot:spring-boot-starter-test"

Step 2: Writing a Simple Spring Boot Test with Testcontainers

@SpringBootTest
@Testcontainers
public class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @Autowired
    private UserRepository userRepository;

    @Test
    void testSaveUser() {
        User user = new User("Alice", "alice@example.com");
        userRepository.save(user);

        assertEquals(1, userRepository.count());
    }
}

Explanation:

  • @Testcontainers tells JUnit 5 to manage container lifecycle.
  • @Container starts a PostgreSQL container automatically.
  • Spring Boot injects the UserRepository for database operations.

Advanced Use Cases

1. Message Brokers with Spring Boot

@SpringBootTest
@Testcontainers
public class KafkaIntegrationTest {

    @Container
    static KafkaContainer kafka = new KafkaContainer("5.5.1");

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Test
    void testSendMessage() {
        kafkaTemplate.send("test-topic", "Hello, Kafka!");
    }
}

2. Reusable Containers with Spring Boot

Instead of restarting containers for every test, you can use static reusable containers.

@Testcontainers
@SpringBootTest
public class SharedContainerTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
            .withReuse(true); // Enables container reuse

    @Test
    void testDatabaseReuse() {
        assertTrue(postgres.isRunning());
    }
}

Integration with CI/CD

Spring Boot apps tested with Testcontainers can run seamlessly in CI/CD pipelines like Jenkins, GitHub Actions, or GitLab CI.

Example GitHub Actions workflow:

name: CI Pipeline
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      docker:
        image: docker:20.10-dind
    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Run Tests
        run: ./mvnw test

Best Practices

  1. Use reusable containers (withReuse(true)) for faster test execution.
  2. Match production versions (e.g., PostgreSQL version in production vs test).
  3. Isolate tests by using unique database names or topics.
  4. Leverage Spring profiles (application-test.yml) for container-specific settings.
  5. Integrate with Docker Compose when multiple services are needed.

Version Tracker

  • JUnit 4 → JUnit 5: Testcontainers integrates more smoothly with JUnit 5 using @Testcontainers.
  • Mockito Updates: Static/final method mocking improvements reduce dependency on PowerMock.
  • Testcontainers Growth: Supports databases, message brokers, LocalStack for AWS, and Kubernetes.

Conclusion and Key Takeaways

  • Testcontainers ensures Spring Boot tests run in production-like environments.
  • It simplifies database, message broker, and cloud service testing.
  • CI/CD pipelines become more reliable with disposable containerized services.
  • By following best practices, teams can scale tests across microservices effectively.

FAQ

Q1. What is the difference between in-memory databases and Testcontainers?
In-memory databases (like H2) differ from real databases. Testcontainers runs actual PostgreSQL/MySQL, eliminating mismatches.

Q2. How can I speed up Testcontainers tests?
Enable container reuse (withReuse(true)) and run tests in parallel.

Q3. Can Testcontainers be used with legacy Spring Boot apps?
Yes, with minimal changes. You can start containers in static blocks and configure properties.

Q4. How does Testcontainers handle network simulation?
It provides Toxiproxy integration for latency and failure testing.

Q5. How does it integrate with Docker Compose?
Testcontainers supports DockerComposeContainer to spin up multiple services like DB + Kafka.

Q6. Is it safe to run Testcontainers in production?
No, it's for testing only. Use real infrastructure in production.

Q7. How can I use Testcontainers with Spring Data JPA?
Simply inject repositories and configure datasource via Testcontainers.

Q8. Does Testcontainers work with Kubernetes?
Yes, using Kind or LocalStack, you can simulate cloud-native deployments.

Q9. How do I run Testcontainers tests in Jenkins?
Ensure Docker is installed and available for the Jenkins agent.

Q10. What are the best practices for large teams?
Use shared container lifecycle management, consistent test profiles, and ensure containers match production.