Introduction to Testcontainers: What and Why?

Illustration for Introduction to Testcontainers: What and Why?
By Last updated:

Testing modern Java applications, especially microservices, is no longer just about unit tests. We need to validate how services interact with real databases, message brokers, and external APIs. But setting up and tearing down these dependencies for every developer and CI pipeline is messy and error-prone.
This is where Testcontainers shines — a powerful library that lets you spin up real, lightweight, disposable containers for integration testing.

In this tutorial, we’ll explore what Testcontainers is, why it’s important, and how it integrates seamlessly with JUnit 5 and Mockito to build reliable, production-ready test suites.


What is Testcontainers?

Testcontainers is a Java library that provides lightweight, throwaway instances of common databases, message brokers, and other services using Docker containers.

Instead of mocking everything or relying on fragile in-memory alternatives, you can test against real infrastructure — PostgreSQL, MySQL, Kafka, Redis, RabbitMQ, LocalStack (for AWS), and more.

Key features:

  • Provides pre-configured Docker containers for popular technologies.
  • Ensures tests run in the same environment locally and in CI/CD.
  • Automatically starts and stops containers with your tests.
  • Works with JUnit 5, Spring Boot, and popular testing frameworks.

Why Do We Need Testcontainers?

Traditional integration tests often suffer from:

  • Environment mismatch: Tests pass locally but fail in CI because of missing dependencies.
  • Heavy setup: Running full external services manually is time-consuming.
  • In-memory fakes: Libraries like H2 don’t always behave like real databases.

Testcontainers solves these issues by giving you disposable, isolated containers for every test run.

👉 Think of it as having a mini production environment inside your test suite.


Example: Using Testcontainers with JUnit 5

Let’s see Testcontainers in action with PostgreSQL.

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

import static org.junit.jupiter.api.Assertions.assertEquals;

@Testcontainers
public class PostgresContainerTest {

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

    @Test
    void testDatabaseConnection() throws Exception {
        try (Connection conn = DriverManager.getConnection(
                postgres.getJdbcUrl(),
                postgres.getUsername(),
                postgres.getPassword())) {

            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE TABLE users(id SERIAL PRIMARY KEY, name VARCHAR(100));");
                stmt.execute("INSERT INTO users(name) VALUES ('Alice');");
                ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM users;");
                rs.next();
                assertEquals(1, rs.getInt(1));
            }
        }
    }
}

Explanation:

  • @Testcontainers → tells JUnit 5 to manage container lifecycle.
  • @Container → defines a PostgreSQL container.
  • The container starts automatically when the test runs and stops after completion.

Output logs will show PostgreSQL container startup, ensuring you’re testing against a real database.


Benefits of Testcontainers in CI/CD

  • Consistent environments: Same Docker images run locally and in Jenkins/GitHub Actions.
  • No manual setup: Containers spin up automatically.
  • Fast feedback: Lightweight images start in seconds.
  • Microservices testing: Run multiple containers (DB + Kafka + Redis) in one test suite.

Real-World Use Cases

  1. Spring Boot + PostgreSQL Integration: Validate JPA/Hibernate mappings against a real DB.
  2. Kafka Event Processing: Test message consumption/production flows with embedded Kafka containers.
  3. REST APIs with LocalStack: Mock AWS services (S3, SQS, DynamoDB) reliably.
  4. Legacy to Modern Migration: Safely validate new microservices before production rollout.

Best Practices for Testcontainers

  • Use @Container with static containers for class-wide reuse.
  • Use lightweight Docker images (alpine) for faster startup.
  • Don’t overuse Testcontainers in unit tests — it’s best for integration/system tests.
  • Combine with Mockito to mock external dependencies where full containers aren’t needed.
  • Cache Docker images in CI to avoid repeated downloads.

Version Tracker

  • JUnit 4 → JUnit 5: Testcontainers has first-class JUnit 5 support (@Testcontainers).
  • Mockito: Evolved to allow mocking static/final methods; combine with Testcontainers for hybrid testing.
  • Testcontainers ecosystem: Added support for more databases, LocalStack, and Docker Compose.

Conclusion

Testcontainers bridges the gap between fast local tests and real-world reliability. Instead of relying only on mocks or in-memory fakes, you get confidence that your application works with real dependencies.

It’s a must-have tool for microservices, cloud-native applications, and CI/CD pipelines.


Key Takeaways

  • Testcontainers enables real integration testing with Docker.
  • Eliminates environment drift between local dev and CI.
  • Works seamlessly with JUnit 5, Mockito, and Spring Boot.
  • Best suited for integration/system tests, not lightweight unit tests.

FAQ

1. What is Testcontainers?
A Java library that provides disposable Docker containers for integration testing.

2. How is it different from mocks like Mockito?
Mocks simulate behavior, Testcontainers gives you the real service in a container.

3. Can I use Testcontainers with JUnit 5?
Yes, it has first-class support using @Testcontainers and @Container.

4. Does it work with Spring Boot?
Yes, you can bootstrap Spring Boot tests with real DBs like PostgreSQL or MySQL.

5. Is it suitable for CI/CD?
Absolutely. Containers run the same way locally and in pipelines.

6. What if Docker is not installed?
Testcontainers requires Docker. Without it, tests will fail.

7. Can I use it with TestNG or Spock?
Yes, it’s framework-agnostic, though JUnit 5 has the cleanest integration.

8. How do I speed up tests with Testcontainers?
Use static containers or reuse mode, and cache images in CI.

9. What services are supported?
Databases, message brokers, Selenium (browsers), LocalStack (AWS), and more.

10. Should I replace all mocks with Testcontainers?
No. Use Testcontainers for integration/system tests and Mockito for unit tests.