testcontainers-integration-tests — Claude Code skill: setup & use
Helps AI agents write .NET integration tests using xUnit and real Docker containers instead of mocks. Useful for verifying database migrations, SQL queries, Redis cache, and message queues.
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers-integration-tests -a claude-code
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a claude-codeWhat testcontainers-integration-tests does
The skill guides agents in writing .NET integration tests with xUnit and Testcontainers 4.x. Instead of mocking IDbConnection or message brokers, it spins up real Docker containers for SQL Server, PostgreSQL, Redis, or RabbitMQ. Tests execute against real schema constraints, indexes, and actual SQL queries. Port randomization avoids local port collisions in CI/CD.
Docker must be available in the environment. The skill targets the Testcontainers 4.x module API, meaning legacy TestcontainersBuilder<T> calls will fail to compile. Module builders require passing the Docker image string directly into the constructor (like new PostgreSqlBuilder("postgres:latest")) rather than calling .WithImage(). Provider packages like Testcontainers.MsSql and Testcontainers.PostgreSql are required.
Containers plug into the xUnit IAsyncLifetime lifecycle. The test fixture starts the container and runs schema migrations during InitializeAsync, then tears it down in DisposeAsync. State isolation between runs relies on Respawn or class-level container reuse.
What it helps with
- test SQL queries and constraints against a real database engine
- verify database migrations without infrastructure mocks
- run Redis caching tests with automatic container cleanup
- test RabbitMQ message flows inside xUnit test suites
Example prompts for the agent
- Write an xUnit integration test for OrderRepository using Testcontainers and PostgreSQL
- Add a Redis integration test using Testcontainers 4.x and StackExchange.Redis
- Replace database mocks in our xUnit suite with MsSqlContainer
How to install testcontainers-integration-tests
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers-integration-tests -a claude-codenpx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a claude-code2Manually: put the files in place
~/.claude/skills/testcontainers/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/testcontainers/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a codex2Manually: put the files in place
.agents/skills/testcontainers/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a cursor2Manually: put the files in place
.cursor/skills/testcontainers/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Aaronontheweb/dotnet-skills testcontainers2Manually: put the files in place
.github/skills/testcontainers/SKILL.md1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a gemini-cli2Platform CLI
gemini skills install https://github.com/Aaronontheweb/dotnet-skills --path skills/testcontainersManually: put the files in place
.devin/skills/testcontainers/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about testcontainers-integration-tests
How do I install testcontainers-integration-tests in Claude Code?
Run npx skills add Aaronontheweb/dotnet-skills --skill testcontainers-integration-tests -a claude-code. Alternatively, place the file at ~/.claude/skills/testcontainers/SKILL.md.
Why use this skill instead of mocking infrastructure?
Mocks do not validate real SQL syntax, database constraints, or index behavior. The skill guides AI agents to launch real Docker containers via IAsyncLifetime for authentic test verification.
Which Testcontainers version does the skill support?
It specifically targets Testcontainers 4.x module builders, such as PostgreSqlBuilder and MsSqlBuilder. Older pre-3.0 builders like TestcontainersBuilder<T> and the .WithImage() method are not supported.
Source text
SKILL.md · MIT
---
name: testcontainers-integration-tests
description: Write integration tests using TestContainers for .NET with xUnit. Covers infrastructure testing with real databases, message queues, and caches in Docker containers instead of mocks.
user-invocable: false
---
# Integration 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](database-patterns.md): SQL Server, PostgreSQL, and migration testing examples
- [infrastructure-patterns.md](infrastructure-patterns.md): Redis, RabbitMQ, multi-container networks, container reuse, and Respawn
## Core Principles
1. **Real Infrastructure Over Mocks** - Use actual databases/services in containers, not mocks
2. **Test Isolation** - Each test gets fresh containers or fresh data
3. **Automatic Cleanup** - TestContainers handles container lifecycle and cleanup
4. **Fast Startup** - Reuse containers across tests in the same class when appropriate
5. **CI/CD Compatible** - Works seamlessly in Docker-enabled CI environments
6. **Port Randomization** - Containers use random ports to avoid conflicts
## TestContainers 4.x API
This skill targets **TestContainers 4.x** with the 3.0+ module builder API. The pre-3.0 types were renamed and removed, so code that uses `TestcontainersBuilder<T>` or `TestcontainersContainer` does **not** compile against the modern packages:
| Old (pre-3.0) | New (3.0+) |
|----------------|------------|
| `new TestcontainersBuilder<T>()` | Module builder (e.g. `new PostgreSqlBuilder(...)`) or `new ContainerBuilder(...)` |
| `TestcontainersContainer` | `DockerContainer` / module container (e.g. `PostgreSqlContainer`) |
| `TestcontainersNetworkBuilder` | `NetworkBuilder` |
| `GetMappedPublicPort(port)` | `GetMappedPublicPort(port)` (still available) |
**Why 4.x and not 3.x?** The module builders did not expose image-taking constructors in Testcontainers 3.0.0. Those constructors (and the deprecation of the parameterless builder constructor) arrived in the 4.x line, so the examples here rely on 4.x behavior. Pin the module packages to a 4.x (or newer) version rather than an unbounded wildcard if you want reproducible builds.
The recommended approach is the **module builders** from the `Testcontainers.<Provider>` packages (see [Required NuGet Packages](#required-nuget-packages)). Each module builder pre-configures the right image, environment variables, ports, and wait strategy for its database or service.
Pass the image to the builder **constructor** (not via `.WithImage()`) to avoid the obsolete parameterless constructor:
```csharp
var container = new PostgreSqlBuilder("postgres:latest")
.WithDatabase("testdb")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();
```
## Why TestContainers Over Mocks?
### The Problem with Mocking Infrastructure
```csharp
// 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
```csharp
// GOOD: Testing against a real database
public class OrderRepositoryTests : IAsyncLifetime
{
private readonly MsSqlContainer _dbContainer;
private SqlConnection _connection;
public OrderRepositoryTests()
{
_dbContainer = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest")
.WithDatabase("TestDb")
.WithPassword("Your_password123")
.Build();
}
public async Task InitializeAsync()
{
await _dbContainer.StartAsync();
_connection = new SqlConnection(_dbContainer.GetConnectionString());
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](database-patterns.md) for complete SQL Server, PostgreSQL, and migration testing examples.
See [infrastructure-patterns.md](infrastructure-patterns.md) for Redis, RabbitMQ, multi-container networks, container reuse, and Respawn database reset patterns.
## Required NuGet Packages
Use the **module packages** — one per provider. They replace the special-casing you'd otherwise write against the core `Testcontainers` package:
```xml
<ItemGroup>
<!-- Core TestContainers -->
<PackageReference Include="Testcontainers" Version="*" />
<!-- Provider modules -->
<PackageReference Include="Testcontainers.MsSql" Version="*" />
<PackageReference Include="Testcontainers.PostgreSql" Version="*" />
<PackageReference Include="Testcontainers.Redis" Version="*" />
<PackageReference Include="Testcontainers.RabbitMq" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<!-- Database drivers -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
<PackageReference Include="Npgsql" Version="*" /> <!-- For PostgreSQL -->
<!-- Other infrastructure -->
<PackageReference Include="StackExchange.Redis" Version="*" /> <!-- For Redis -->
<PackageReference Include="RabbitMQ.Client" Version="*" /> <!-- For RabbitMQ -->
</ItemGroup>
```
## Required Usings
The module builders and container types live in provider-specific namespaces. The base types (`ContainerBuilder`, `NetworkBuilder`, `Wait`) live in `DotNet.Testcontainers.Builders`, and `INetwork` lives in `DotNet.Testcontainers.Networks`:
```csharp
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Networks;
using Testcontainers.MsSql;
using Testcontainers.PostgreSql;
using Testcontainers.Redis;
using Testcontainers.RabbitMq;
```
## Best Practices
1. **Always Use IAsyncLifetime** - Proper async setup and teardown
2. **Wait for Port Availability** - Use a `Wait` strategy to ensure containers are ready
3. **Use Random Ports** - Let TestContainers assign ports automatically
4. **Clean Data Between Tests** - Either use fresh containers or truncate tables
5. **Reuse Containers When Possible** - Faster than creating new ones for each test
6. **Test Real Queries** - Don't just test mocks; verify actual SQL behavior
7. **Verify Constraints** - Test foreign keys, unique constraints, indexes
8. **Test Transactions** - Verify rollback and commit behavior
9. **Use Realistic Data** - Test with production-like data volumes
10. **Handle Cleanup** - Always dispose containers in `DisposeAsync`
## Common Issues and Solutions
### Container Startup Timeout
```csharp
_container = new PostgreSqlBuilder("postgres:latest")
.WithDatabase("testdb")
.WithUsername("postgres")
.WithPassword("postgres")
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilInternalTcpPortIsAvailable(5432, w => w.WithTimeout(TimeSpan.FromMinutes(2))))
.Build();
```
### Port Already in Use
Always use random port mapping. Module builders handle this by default, but the core container builder exposes it explicitly:
```csharp
.WithPortBinding(5432, true) // true = assign random public port
```
### Containers Not Cleaning Up
Ensure proper disposal:
```csharp
public async Task DisposeAsync()
{
await _connection?.DisposeAsync();
await _container?.DisposeAsync();
}
```
### Tests Fail in CI But Pass Locally
Ensure CI has Docker support:
```yaml
# GitHub Actions
runs-on: ubuntu-latest # Has Docker pre-installed
```
## CI/CD Integration
### GitHub Actions
```yaml
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
1. **Reuse containers** - Share fixtures across tests in a collection
2. **Use Respawn** - Reset data without recreating containers
3. **Parallel execution** - TestContainers handles port conflicts automatically
4. **Use lightweight images** - Alpine versions are smaller and faster
5. **Cache images** - Docker will cache pulled images locally
© Aaronontheweb · MIT · Aaronontheweb/dotnet-skills