Posted in

Integration Testing with TestContainers: From Theory to Practice

Integration testing is hard. Developers often face two extremes:

  • Mocks that make tests fast but unrealistic
  • Shared test environments that are slow, fragile, and hard to maintain

Mocks can give a false sense of security—tests pass because mocks behave exactly as expected, not because the code works with real systems. Who guarantees that your mock behaves like a real PostgreSQL database or Kafka queue?

The alternative—dedicated environments—brings its own headaches:

  • How do you reset state between tests?
  • How do you prevent tests from interfering with each other?
  • How do you ensure the environment matches production?

Enter TestContainers: a pragmatic solution that spins up real services in ephemeral Docker containers for each test—fast, isolated, and automatically cleaned up.


What Are TestContainers?

TestContainers is a library that lets you programmatically control Docker containers in your tests. Instead of writing mocks or maintaining manual environments, your test spins up the required infrastructure on demand and tears it down afterward.

Core Concept:

  • Start one or more containers
  • Wait until they’re ready
  • Run your test logic
  • Stop and clean up automatically

TestContainers handles dynamic port mapping, resource cleanup, and even failure recovery via a built-in Reaper mechanism.


Why TestContainers?

  • Realistic tests: Use actual services, not mocks
  • Isolation: Each test gets a fresh container
  • Automation: No manual cleanup or forgotten resources
  • Flexibility: Works with databases, message queues, and custom services

Key Features

Wait Strategies

A container isn’t ready immediately after starting. TestContainers uses wait strategies to determine readiness:

  • Port listening
  • Successful HTTP response
  • Specific log output

Prebuilt Modules

TestContainers offers modules for common services:

These modules provide sensible defaults—ports, environment variables, and wait strategies—so you can start testing quickly.

Custom Containers

For less common services, you can create custom wrappers using GenericContainer. Define:

  • Image
  • Ports
  • Startup commands
  • Client initialization logic

TestContainers vs Docker Compose

Both can start containers for tests, but the approach differs:

  • Docker Compose: Declarative YAML, shared environment for multiple tests
  • TestContainers: Programmatic, per-test isolation, dynamic lifecycle

With Compose, you must manually reset containers between tests. TestContainers is designed for automatic isolation—each test gets a clean slate.


Language Support

TestContainers isn’t a single monolithic project—it’s an idea implemented in multiple languages:

  • Java
  • Go
  • .NET
  • Node.js
  • Python
  • PHP
  • Rust

APIs differ, and features vary by language. Always check the documentation for your implementation.


Practical Example: EventSourcingDB

EventSourcingDB, a specialized database for event sourcing, starts in under a second—perfect for the “container per test” approach. Its client SDKs for Go and PHP include TestContainers integrations, illustrating how different languages implement the same concept.


Best Practices for Using TestContainers

  1. Use fast-starting services for per-test containers
  2. Apply wait strategies to ensure readiness
  3. Leverage prebuilt modules for common services
  4. Create custom wrappers for proprietary systems
  5. Monitor resource usage—containers consume CPU and memory
  6. Run tests in CI/CD with Docker installed

Common Pitfalls

  • Assuming feature parity across languages
  • Ignoring container startup time for heavy services
  • Forgetting to configure cleanup in custom wrappers

FAQs

1) What is TestContainers?

A library for running real services in Docker containers during integration tests.

2) Why not just use mocks?

Mocks can’t guarantee real-world behavior. TestContainers uses actual services for realistic tests.

3) Is Docker required?

Yes, TestContainers relies on Docker for container orchestration.

4) Can I use TestContainers in CI/CD?

Absolutely—just ensure Docker is available in your pipeline.

5) Does TestContainers support all languages?

It supports many languages, but features vary. Check your language’s documentation.


Conclusion

TestContainers bridges the gap between fast but unrealistic mocks and slow, fragile test environments. By running real services in ephemeral containers, you get reliable, isolated, and automated integration tests—without sacrificing speed.

Leave a Reply

Your email address will not be published. Required fields are marked *