New to Claude Skills? Learn how to install them →

aaronontheweb on GitHub

Integration Testing with TestContainers

Free

Run integration tests with real infrastructure in Docker.

Get this skill

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 source

1. Install with the skills CLI

npx skills add aaronontheweb/dotnet-skills/testcontainers --agent claude-code

2. 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 aaronontheweb

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

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

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

  1. Always Use IAsyncLifetime - Proper async setup and teardown
  2. Wait for Port Availability - Use WaitStrategy 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

_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

  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

Frequently asked questions about Integration Testing with TestContainers

Similar skills