
Integration Testing with TestContainers
FreeRun integration tests with real infrastructure in Docker.
Free · Opens the source repo
What Integration Testing with TestContainers does
The Integration Testing with TestContainers skill enables developers to write robust integration tests using real infrastructure components such as databases, message queues, and caches, all encapsulated in Docker containers. This approach eliminates the pitfalls associated with mocking, providing a more accurate representation of how applications will behave in production environments. By utilizing TestContainers, you can ensure that your tests interact with actual services, which leads to more reliable and meaningful test outcomes.
This skill is particularly useful for testing data access layers against real databases, verifying message queue integrations, and assessing caching behavior with systems like Redis. It supports a variety of databases including SQL Server, PostgreSQL, and MySQL, allowing for comprehensive testing of database migrations and schema changes. The skill also emphasizes test isolation, ensuring that each test runs in a clean environment, thus avoiding side effects from previous tests.
The integration tests are designed to be CI/CD compatible, making it easy to incorporate them into automated pipelines. The skill provides best practices for setting up tests, such as using async lifecycle management and ensuring proper cleanup of containers. With features like port randomization and automatic cleanup, developers can focus on writing tests without worrying about environment conflicts or leftover state.
Overall, this skill is aimed at developers who prioritize test accuracy and reliability, especially those working on applications that heavily rely on various infrastructure components. By using real services in a controlled manner, you can gain confidence in your application's behavior before it reaches production.
When to use it
Use this skill when you need to write integration tests that require interaction with actual databases, message queues, or caching systems.
When not to use it
This skill may not be suitable for unit testing scenarios where mocks are sufficient or when testing in environments without Docker support.
What you can build with it
Testing Data Access Layers
Use this skill to verify that your application's data access layers interact correctly with real databases, ensuring that SQL queries and constraints behave as expected.
Verifying Message Queue Integrations
When integrating with message queues like RabbitMQ, this skill allows you to test the actual message flow and processing behavior in a controlled environment.
Assessing Caching Behavior
Utilize this skill to test how your application interacts with caching systems like Redis, ensuring that caching logic works correctly under real-world conditions.
How to install Integration Testing with TestContainers
View source1. Install with the skills CLI
npx skills add aaronontheweb/dotnet-skills/testcontainers --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by aarononthewebIntegration Testing with TestContainers
When to Use This Skill
Use this skill when:
- Writing integration tests that need real infrastructure (databases, caches, message queues)
- Testing data access layers against actual databases
- Verifying message queue integrations
- Testing Redis caching behavior
- Avoiding mocks for infrastructure components
- Ensuring tests work against production-like environments
- Testing database migrations and schema changes
Reference Files
- database-patterns.md: SQL Server, PostgreSQL, and migration testing examples
- infrastructure-patterns.md: Redis, RabbitMQ, multi-container networks, container reuse, and Respawn
Core Principles
- Real Infrastructure Over Mocks - Use actual databases/services in containers, not mocks
- Test Isolation - Each test gets fresh containers or fresh data
- Automatic Cleanup - TestContainers handles container lifecycle and cleanup
- Fast Startup - Reuse containers across tests in the same class when appropriate
- CI/CD Compatible - Works seamlessly in Docker-enabled CI environments
- Port Randomization - Containers use random ports to avoid conflicts
Why TestContainers Over Mocks?
The Problem with Mocking Infrastructure
// BAD: Mocking a database
public class OrderRepositoryTests
{
private readonly Mock<IDbConnection> _mockDb = new();
[Fact]
public async Task GetOrder_ReturnsOrder()
{
// This doesn't test real SQL behavior, constraints, or performance
_mockDb.Setup(db => db.QueryAsync<Order>(It.IsAny<string>()))
.ReturnsAsync(new[] { new Order { Id = 1 } });
var repo = new OrderRepository(_mockDb.Object);
var order = await repo.GetOrderAsync(1);
Assert.NotNull(order);
}
}
Problems: doesn't test actual SQL queries, misses constraints/indexes, gives false confidence, doesn't catch SQL syntax errors.
Better: TestContainers with Real Database
// GOOD: Testing against a real database
public class OrderRepositoryTests : IAsyncLifetime
{
private readonly TestcontainersContainer _dbContainer;
private IDbConnection _connection;
public OrderRepositoryTests()
{
_dbContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment("SA_PASSWORD", "Your_password123")
.WithPortBinding(1433, true)
.Build();
}
public async Task InitializeAsync()
{
await _dbContainer.StartAsync();
var port = _dbContainer.GetMappedPublicPort(1433);
var connectionString = $"Server=localhost,{port};Database=TestDb;User Id=sa;Password=Your_password123;TrustServerCertificate=true";
_connection = new SqlConnection(connectionString);
await _connection.OpenAsync();
await RunMigrationsAsync(_connection);
}
public async Task DisposeAsync()
{
await _connection.DisposeAsync();
await _dbContainer.DisposeAsync();
}
[Fact]
public async Task GetOrder_WithRealDatabase_ReturnsOrder()
{
await _connection.ExecuteAsync(
"INSERT INTO Orders (Id, CustomerId, Total) VALUES (1, 'CUST1', 100.00)");
var repo = new OrderRepository(_connection);
var order = await repo.GetOrderAsync(1);
Assert.NotNull(order);
Assert.Equal("CUST1", order.CustomerId);
Assert.Equal(100.00m, order.Total);
}
}
See database-patterns.md for complete SQL Server, PostgreSQL, and migration testing examples.
See infrastructure-patterns.md for Redis, RabbitMQ, multi-container networks, container reuse, and Respawn database reset patterns.
Required NuGet Packages
<ItemGroup>
<PackageReference Include="Testcontainers" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<!-- Database-specific packages -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
<PackageReference Include="Npgsql" Version="*" /> <!-- For PostgreSQL -->
<PackageReference Include="MySqlConnector" Version="*" /> <!-- For MySQL -->
<!-- Other infrastructure -->
<PackageReference Include="StackExchange.Redis" Version="*" /> <!-- For Redis -->
<PackageReference Include="RabbitMQ.Client" Version="*" /> <!-- For RabbitMQ -->
</ItemGroup>
Best Practices
- Always Use IAsyncLifetime - Proper async setup and teardown
- Wait for Port Availability - Use
WaitStrategyto ensure containers are ready - Use Random Ports - Let TestContainers assign ports automatically
- Clean Data Between Tests - Either use fresh containers or truncate tables
- Reuse Containers When Possible - Faster than creating new ones for each test
- Test Real Queries - Don't just test mocks; verify actual SQL behavior
- Verify Constraints - Test foreign keys, unique constraints, indexes
- Test Transactions - Verify rollback and commit behavior
- Use Realistic Data - Test with production-like data volumes
- Handle Cleanup - Always dispose containers in
DisposeAsync
Common Issues and Solutions
Container Startup Timeout
_container = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("postgres:latest")
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilPortIsAvailable(5432)
.WithTimeout(TimeSpan.FromMinutes(2)))
.Build();
Port Already in Use
Always use random port mapping:
.WithPortBinding(5432, true) // true = assign random public port
Containers Not Cleaning Up
Ensure proper disposal:
public async Task DisposeAsync()
{
await _connection?.DisposeAsync();
await _container?.DisposeAsync();
}
Tests Fail in CI But Pass Locally
Ensure CI has Docker support:
# GitHub Actions
runs-on: ubuntu-latest # Has Docker pre-installed
CI/CD Integration
GitHub Actions
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: Run Integration Tests
run: |
dotnet test tests/YourApp.IntegrationTests \
--filter Category=Integration \
--logger trx
- name: Cleanup Containers
if: always()
run: docker container prune -f
Performance Tips
- Reuse containers - Share fixtures across tests in a collection
- Use Respawn - Reset data without recreating containers
- Parallel execution - TestContainers handles port conflicts automatically
- Use lightweight images - Alpine versions are smaller and faster
- Cache images - Docker will cache pulled images locally
Frequently asked questions about Integration Testing with TestContainers
Similar skills
ClickHouse Logs Queries
Efficiently manage Supabase logs with ClickHouse SQL.
EF Core D2 Database Diagram Generator
Visualize your EF Core models as D2 diagrams effortlessly.
Safe SQL Execution
Ensure secure SQL execution in Supabase applications.
Oracle to PostgreSQL Migration
Identify migration risks between Oracle and PostgreSQL.
SSMA Console
Streamline Oracle to SQL Server migrations with ease.
SQL Performance Optimization
Enhance SQL query efficiency across all databases.
