Testcontainers Integration Testing
Intent
Ensure that a component's interaction with its immediate infrastructure (PostgreSQL, Redis, RabbitMQ) is validated against real services running in Docker, rather than mocks or in-memory substitutes. This provides high confidence in data access, migrations, and infrastructure-specific behaviour.
When to Use
- •Testing data access layers against actual database engines (e.g., PostgreSQL instead of EF InMemory).
- •Verifying database migrations and schema constraints.
- •Testing message queue publishing/consumption.
- •Validating caching behaviour with real Redis.
- •Implementing "ComponentE2E" tests as defined in the 5-tier strategy.
Precondition Failure Signal
- •Database logic is "tested" via mocks that ignore SQL syntax, constraints, or triggers.
- •In-memory providers are used that don't match production database behaviour.
- •Infrastructure-related bugs are only caught in CI or production.
- •Tests depend on a shared, pre-configured database instance.
Postcondition Success Signal
- •Tests use Testcontainers to spin up isolated, fresh instances of required services.
- •Container lifecycle (start/stop) is managed automatically by the test fixture.
- •Dynamic port mapping is used to avoid host-level conflicts.
- •Tests demonstrate successful interaction with real infrastructure (e.g., records persisted to real SQL).
Process
- •Source Review: Identify the infrastructure dependencies of the component under test.
- •Implementation:
- •Define a
Testcontainersbuilder with the appropriate Docker image. - •Configure random port binding and wait strategies.
- •Implement
IAsyncLifetimeto start the container and initialize connection strings.
- •Define a
- •Verification: Run the tests and confirm they interact correctly with the containerized service.
- •Documentation: Document the required Docker images and any special configuration.
- •Review: Platform/DevOps Engineer and Tech Lead review the container configuration and disposal logic.
Key Practices
- •Use wait strategies and random port binding for reliable startup.
- •Manage container lifecycle with
IAsyncLifetimeand clean teardown. - •Reuse containers only when it reduces runtime without compromising isolation.
- •Run migrations in fixture setup and keep seed data minimal.
Example Test / Validation
- •Pattern: PostgreSQL Repository Test.
- •Start PostgreSQL container.
- •Run migrations.
- •Insert record.
- •Query record and assert values match.
Common Red Flags / Guardrail Violations
- •Hard-coding host ports (e.g.,
.WithPortBinding(5432, 5432)). - •Forgetting to dispose of containers in
DisposeAsync. - •Tests that are slow because containers are recreated for every single test (use
ICollectionFixtureto reuse where appropriate). - •Using Testcontainers for cross-component testing (use
aspire-integration-testinginstead).
Recommended Review Personas
- •Platform/DevOps Engineer – validates Docker usage and CI compatibility.
- •Tech Lead – validates that tests cover real-world infrastructure edge cases.
Skill Priority
P1 – Quality & Correctness
Conflict Resolution Rules
- •Testcontainers are preferred over mocks for "ComponentE2E" / Infrastructure testing.
- •If multiple infrastructure components are involved across services, prefer
aspire-integration-testing.
Conceptual Dependencies
- •quality-gate-enforcement
- •test-driven-development
Classification
Core Operational
Notes
Testcontainers ensures "ComponentE2E" tests remain co-located with the component code and run reliably in any Docker-enabled environment.
See skills/testcontainers-integration-tests/references/reference.md for examples and CI notes.