Message brokers like Apache Kafka and RabbitMQ are the backbone of event-driven and microservices architectures. They enable asynchronous communication, decoupled services, and reliable message delivery. But testing applications that depend on message brokers is notoriously hard.
Enter Testcontainers: a library that allows developers to spin up real Kafka or RabbitMQ instances inside Docker containers for integration tests. Unlike mocks or stubs, Testcontainers ensures your tests behave as close to production as possible.
In this tutorial, we’ll explore how to set up and use Testcontainers for Kafka and RabbitMQ in Java projects.
Why Use Testcontainers for Message Brokers?
- Realistic testing – no need for fragile mocks; you test against actual Kafka/RabbitMQ brokers.
- Ephemeral environments – containers are disposable and isolated per test run.
- CI/CD ready – ensures consistent results in Jenkins, GitHub Actions, and GitLab CI.
- Developer productivity – eliminates manual broker setup and environment drift.
Setting Up Testcontainers in Your Project
Maven Dependency
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers BOM -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.20.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Kafka Module -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>kafka</artifactId>
<scope>test</scope>
</dependency>
<!-- RabbitMQ Module -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>rabbitmq</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Example 1: Kafka with Testcontainers
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.KafkaContainer;
import org.testcontainers.utility.DockerImageName;
import static org.assertj.core.api.Assertions.assertThat;
class KafkaTestcontainersTest {
@Test
void testKafkaBrokerStartup() {
KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));
kafka.start();
String bootstrapServers = kafka.getBootstrapServers();
System.out.println("Kafka running at: " + bootstrapServers);
assertThat(bootstrapServers).contains("PLAINTEXT://");
kafka.stop();
}
}
What Happens Here?
- A Kafka container is started.
- We fetch the broker URL dynamically.
- You can then use this broker with any Kafka producer/consumer test code.
Example 2: RabbitMQ with Testcontainers
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.RabbitMQContainer;
import static org.assertj.core.api.Assertions.assertThat;
class RabbitMQTestcontainersTest {
@Test
void testRabbitMQBrokerStartup() {
try (RabbitMQContainer rabbit = new RabbitMQContainer("rabbitmq:3.12-management")) {
rabbit.start();
String uri = rabbit.getAmqpUrl();
System.out.println("RabbitMQ running at: " + uri);
assertThat(uri).contains("amqp://");
}
}
}
Key Benefits
- Automatic port mapping – no conflicts with local services.
- Built-in management UI (at port 15672).
- Can preload queues, exchanges, and bindings for test scenarios.
Advanced Use Cases
1. Preloading Kafka Topics
kafka.withEnv("KAFKA_CREATE_TOPICS", "orders:1:1,users:1:1");
2. RabbitMQ with Custom Exchange
rabbit.withQueue("test-queue", true, false, null)
.withExchange("test-exchange", "direct")
.withBinding("test-exchange", "test-queue");
Best Practices
- Use @Testcontainers and @Container annotations for automatic lifecycle management.
- Keep tests isolated: each test should not depend on another.
- Use Testcontainers Reuse Mode for faster local dev cycles (
~/.testcontainers.properties
). - Avoid over-mocking: rely on real broker containers for integration testing.
Version Tracker
- JUnit 4 → JUnit 5 –
@Test
annotations remain, but lifecycle improved. - Mockito – now supports static/final mocking for edge cases.
- Testcontainers – expanded modules for Kafka, RabbitMQ, LocalStack, Kubernetes.
Conclusion
By leveraging Testcontainers with Kafka and RabbitMQ, developers can confidently test event-driven and microservices applications. This approach reduces flaky tests, ensures production-like reliability, and integrates seamlessly into CI/CD pipelines.
Key Takeaways
- Testcontainers enables realistic integration tests for message brokers.
- Kafka and RabbitMQ containers start ephemerally for each test run.
- Works perfectly with JUnit 5 and Mockito in modern Java projects.
- Recommended for microservices, distributed systems, and event-driven architectures.
FAQ
1. Why not just mock Kafka or RabbitMQ?
Mocks miss critical broker behaviors like retries, partitions, and acknowledgments. Testcontainers tests against the real system.
2. Can I run Kafka and RabbitMQ containers in parallel?
Yes, Testcontainers isolates ports and networks, so both can run side by side.
3. How heavy are these containers?
Kafka and RabbitMQ images are larger than mocks, but they ensure realistic testing.
4. Can I preload test data into RabbitMQ queues?
Yes, with withQueue()
and withExchange()
methods.
5. Is it suitable for CI/CD?
Absolutely. Testcontainers is designed for ephemeral environments like Jenkins and GitHub Actions.
6. Can I use Docker Compose instead of Testcontainers?
You can, but Testcontainers gives fine-grained programmatic control.
7. What if my dev machine doesn’t have Docker?
Testcontainers requires Docker; use lightweight engines like Rancher Desktop or Colima.
8. Does Testcontainers support clustered Kafka or RabbitMQ?
Yes, but single-node brokers are most common for test scenarios.
9. How do I debug container logs?
Use kafka.getLogs()
or rabbit.getLogs()
in your tests.
10. Is this production-ready?
No, Testcontainers is for testing only, not production workloads.