10.7Best Practices
Use
scope="module"-- Starting containers is slow (2-10 seconds each). Share containers across tests in a module, not per-test.Use fixtures for cleanup -- Each test should get a clean state (truncated tables, flushed caches) via fixtures, not by starting new containers.
Pin image versions -- Use
postgres:16-alpine, notpostgres:latest. Tests must be reproducible.Set wait strategies -- Some containers need custom health checks:
from testcontainers.core.waiting_utils import wait_for_logs
with KafkaContainer("confluentinc/cp-kafka:7.6.0") as kafka:
wait_for_logs(kafka, "started (kafka.server.KafkaServer)", timeout=60)
Handle port randomization -- Testcontainers assigns random host ports. Always use
container.get_exposed_port()rather than hardcoding ports.Run in CI with Docker-in-Docker or Docker socket -- CI environments need Docker access. Most CI providers support this natively.
# GitHub Actions: Docker is available by default
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install testcontainers pytest
- run: pytest tests/integration/ -v
Testcontainers has become the standard for integration testing because it eliminates the "works locally, fails in CI" problem. Real services in Docker are more reliable than mocks and more portable than shared test environments.