Skip to main content
skill CX Codex

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.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers-integration-tests -a claude-code
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a claude-code
Open on GitHub

What 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-code
npx skills add Aaronontheweb/dotnet-skills --skill testcontainers -a claude-code

2Manually: put the files in place

~/.claude/skills/testcontainers/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/testcontainers/

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

Similar skills

Everything for Testing and QA →

From the same set32

microsoft-extensions-configuration A skill for Claude and other AI editors that guides binding appsettings.json to strongly-typed classes and validating configuration at startup. CX Codex +1 577installs serialization Tells the agent which serialization format to use in .NET: schema-based over reflection, System.Text.Json with source generators for JSON. Useful when designing APIs, messaging or event storage. CX Codex +1 569installs crap-analysis A skill that computes CRAP scores from coverage and cyclomatic complexity to flag .NET code that is risky to touch. CX Codex +1 556installs dotnet-slopwatch Detects LLM reward hacking in .NET code, catching disabled tests, silenced compiler warnings, and empty catch blocks. It validates changes right after an agent makes them. CX Codex +1 552installs playwright-blazor-testing A skill that walks your agent through Playwright end-to-end tests for Blazor Server and WebAssembly: navigation, forms, auth, selectors. CX Codex +1 537installs dotnet-local-tools A skill that walks through per-repository .NET CLI tools via .config/dotnet-tools.json, so local dev and CI restore the same versions. CX Codex +1 526installs aspire-integration-testing A skill for writing .NET Aspire integration tests with xUnit: fixtures, distributed application startup, endpoint discovery. CX Codex +1 499installs mjml-email-templates A skill that gets your agent writing transactional emails in MJML and compiling them to HTML that holds up in Outlook, Gmail and Apple Mail. CX Codex +1 492installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor