Performance Testing with Testcontainers: A Practical Guide for Java Developers

Illustration for Performance Testing with Testcontainers: A Practical Guide for Java Developers
By Last updated:

Performance testing is no longer optional in modern software development—it’s essential. As applications scale, the ability to validate responsiveness, throughput, and reliability under load ensures better user experiences and operational stability. Testcontainers brings a game-changing approach to performance testing by providing ephemeral, production-like environments directly within your test suite.

In this tutorial, we’ll explore how to conduct performance testing with Testcontainers in Java projects. You’ll learn how to set up Testcontainers for load and stress testing, integrate with tools like JUnit 5, Gatling, and JMeter, and apply best practices for reliable CI/CD pipelines.


What is Performance Testing?

Performance testing validates how an application behaves under expected and peak loads. Common types include:

  • Load Testing – Simulate real-world user traffic.
  • Stress Testing – Push the system beyond normal capacity.
  • Spike Testing – Introduce sudden bursts of traffic.
  • Endurance Testing – Measure behavior under sustained load.

Traditional test setups often use static environments, which can be brittle and expensive. Testcontainers solves this by running real services (databases, message brokers, APIs) in isolated Docker containers—ideal for repeatable and disposable performance tests.


Why Use Testcontainers for Performance Testing?

  1. Production-like Environments – Test against real PostgreSQL, MySQL, Kafka, or Redis containers.
  2. Disposable and Isolated – Avoid flaky tests caused by shared test infrastructure.
  3. CI/CD Friendly – Run consistent tests across developer machines and pipelines.
  4. Integration with Load Tools – Combine Testcontainers with JMeter or Gatling for realistic traffic simulation.

Analogy: Think of Testcontainers as a pop-up lab where you can quickly spin up the same infrastructure your app uses in production—without polluting your local machine.


Setting Up Testcontainers for Performance Testing

Maven Dependency

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

Example: PostgreSQL Container

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

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

public class PerformanceTest {

    @Test
    void startPostgresForLoadTest() {
        try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")) {
            postgres.start();

            String jdbcUrl = postgres.getJdbcUrl();
            String username = postgres.getUsername();
            String password = postgres.getPassword();

            System.out.println("Database ready at: " + jdbcUrl);

            assertTrue(postgres.isRunning());
        }
    }
}

This ensures each test run starts with a fresh database—ideal for consistent performance benchmarks.


Integrating with JMeter or Gatling

While Testcontainers sets up infrastructure, you can drive load using JMeter or Gatling:

Gatling Example (Kotlin DSL)

class LoadSimulation : Simulation() {

    val httpProtocol = http.baseUrl("http://localhost:8080")

    val scn = scenario("Basic Load Test")
        .exec(http("Health Check").get("/actuator/health"))

    init {
        setUp(scn.injectOpen(constantUsersPerSec(50).during(30))).protocols(httpProtocol)
    }
}

Combine this with Testcontainers’ GenericContainer to spin up dependent services before executing the Gatling simulation.


Using Testcontainers for Message Brokers in Performance Tests

Example: Kafka container for throughput validation.

import org.testcontainers.containers.KafkaContainer;
import org.testcontainers.utility.DockerImageName;

public class KafkaPerformanceTest {
    public static void main(String[] args) {
        KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));
        kafka.start();
        System.out.println("Kafka broker running at: " + kafka.getBootstrapServers());
    }
}

You can now benchmark producer/consumer latency under heavy traffic.


Best Practices for Performance Testing with Testcontainers

  • Isolate Test Data – Use ephemeral containers for clean runs.
  • Run in CI/CD – Integrate with GitHub Actions or Jenkins.
  • Use Resource Limits – Configure CPU/memory for realistic performance.
  • Parallel Execution – Scale test coverage with JUnit 5 parallelism.
  • Measure Metrics – Collect response times, GC logs, and DB query latency.

Case Study: Spring Boot Microservice

Imagine testing a Spring Boot app with PostgreSQL + Redis. Testcontainers lets you spin up both, run Gatling load tests, and validate cache hit ratios and query response times—without provisioning external environments.


Version Tracker

  • JUnit 4 → JUnit 5: Modern annotations and better parallelism.
  • Mockito: Now supports mocking final/static methods.
  • Testcontainers: Added support for Docker Compose, LocalStack, and reusable containers.

Conclusion & Key Takeaways

  • Testcontainers bridges the gap between functional correctness and realistic performance testing.
  • You can simulate databases, brokers, and services under load conditions.
  • When combined with Gatling/JMeter, you achieve end-to-end, production-like benchmarks.

FAQ

Q1. What’s the difference between unit and performance tests?
Unit tests check correctness; performance tests validate speed, scalability, and reliability.

Q2. Can I use Mockito in performance tests?
Yes, but mocks are more suited for unit tests. Performance tests should use real services with Testcontainers.

Q3. How does Testcontainers help CI/CD pipelines?
It ensures tests run the same in local and pipeline environments, reducing flaky results.

Q4. Can I combine Testcontainers with JUnit 5 dynamic tests?
Absolutely. Use dynamic tests for multiple load scenarios.

Q5. How do I simulate latency in performance tests?
Combine Testcontainers with network simulation tools (toxiproxy) to add latency or packet loss.

Q6. Can I use Testcontainers for stress testing?
Yes—spin up clusters of services and push traffic via Gatling/JMeter.

Q7. What’s the advantage of ephemeral containers?
They ensure repeatable tests without leftover state.

Q8. How do I fix flaky performance tests?
Use dedicated containers, control system resources, and rerun with consistent workloads.

Q9. Does Testcontainers support Kubernetes?
Yes, it integrates with LocalStack, Kind, and cloud-native tools for realistic testing.

Q10. Should performance tests run in CI/CD or dedicated pipelines?
Both are possible. Lightweight tests in CI/CD, heavy load tests in dedicated nightly pipelines.