Testes de Integração com TestContainers

Escreve testes de integração usando TestContainers para .NET com xUnit. Cobre testes de infraestrutura com bancos de dados reais, filas de mensagens e caches em contêineres Docker, em vez de mocks.

O que é Testes de Integração com TestContainers?

Este skill capacita desenvolvedores a escreverem testes de integração robustos para .NET usando a biblioteca TestContainers, substituindo mocks por ambientes reais de infraestrutura executados em contêineres Docker. Ele suporta bancos de dados como SQL Server e PostgreSQL, filas de mensagens como RabbitMQ e cache Redis, garantindo que os testes reflitam o comportamento de produção. O skill fornece princípios fundamentais como isolamento de testes, limpeza automática de contêineres e reutilização de contêineres para maior desempenho. Inclui exemplos práticos em C#, configurações de pacotes NuGet, integração com CI/CD (GitHub Actions) e boas práticas para evitar problemas comuns como timeouts de inicialização e conflitos de porta. Ao utilizar TestContainers, os desenvolvedores ganham confiança real nos testes, verificando consultas SQL, restrições e transações em ambientes semelhantes à produção.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/testcontainers

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que Testes de Integração com TestContainers faz?

Quando Usar Este Skill

Use este skill quando:

  • Escrever testes de integração que precisam de infraestrutura real (bancos de dados, caches, filas de mensagens)
  • Testar camadas de acesso a dados em bancos de dados reais
  • Verificar integrações com filas de mensagens
  • Testar o comportamento de cache com Redis
  • Evitar mocks para componentes de infraestrutura
  • Garantir que os testes funcionem em ambientes semelhantes à produção
  • Testar migrações e alterações de esquema de banco de dados

Arquivos de Referência

Princípios Fundamentais

  1. Infraestrutura Real em vez de Mocks - Use bancos de dados/serviços reais em contêineres, não mocks
  2. Isolamento de Testes - Cada teste recebe contêineres ou dados novos
  3. Limpeza Automática - TestContainers gerencia o ciclo de vida e a limpeza dos contêineres
  4. Inicialização Rápida - Reutilize contêineres entre testes na mesma classe quando apropriado
  5. Compatível com CI/CD - Funciona perfeitamente em ambientes CI com Docker habilitado
  6. Randomização de Portas - Contêineres usam portas aleatórias para evitar conflitos

Por Que TestContainers em vez de Mocks?

O Problema de Mockar Infraestrutura

// RUIM: Mockando um banco de dados
public class OrderRepositoryTests
{
    private readonly Mock<IDbConnection> _mockDb = new();

    [Fact]
    public async Task GetOrder_ReturnsOrder()
    {
        // Isso não testa o comportamento real do SQL, restrições ou desempenho
        _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);
    }
}

Problemas: não testa consultas SQL reais, ignora restrições/índices, dá falsa confiança, não captura erros de sintaxe SQL.

Melhor: TestContainers com Banco de Dados Real

// BOM: Testando contra um banco de dados real
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);
    }
}

Consulte database-patterns.md para exemplos completos de SQL Server, PostgreSQL e testes de migração.

Consulte infrastructure-patterns.md para exemplos de Redis, RabbitMQ, redes multi-contêiner, reutilização de contêineres e padrões de reset de banco de dados com Respawn.

Pacotes NuGet Necessários

<ItemGroup>
  <PackageReference Include="Testcontainers" Version="*" />
  <PackageReference Include="xunit" Version="*" />
  <PackageReference Include="xunit.runner.visualstudio" Version="*" />

  <!-- Pacotes específicos de banco de dados -->
  <PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
  <PackageReference Include="Npgsql" Version="*" /> <!-- Para PostgreSQL -->
  <PackageReference Include="MySqlConnector" Version="*" /> <!-- Para MySQL -->

  <!-- Outras infraestruturas -->
  <PackageReference Include="StackExchange.Redis" Version="*" /> <!-- Para Redis -->
  <PackageReference Include="RabbitMQ.Client" Version="*" /> <!-- Para RabbitMQ -->
</ItemGroup>

Melhores Práticas

  1. Sempre Use IAsyncLifetime - Configuração e desmontagem assíncrona adequadas
  2. Aguarde a Disponibilidade da Porta - Use WaitStrategy para garantir que os contêineres estejam prontos
  3. Use Portas Aleatórias - Deixe o TestContainers atribuir portas automaticamente
  4. Limpe Dados entre Testes - Use contêineres novos ou trunque tabelas
  5. Reutilize Contêineres Quando Possível - Mais rápido do que criar novos para cada teste
  6. Teste Consultas Reais - Não teste apenas mocks; verifique o comportamento real do SQL
  7. Verifique Restrições - Teste chaves estrangeiras, restrições únicas, índices
  8. Teste Transações - Verifique o comportamento de rollback e commit
  9. Use Dados Realistas - Teste com volumes de dados semelhantes aos de produção
  10. Lide com a Limpeza - Sempre descarte os contêineres em DisposeAsync

Problemas Comuns e Soluções

Tempo Limite de Inicialização do Contêiner

_container = new TestcontainersBuilder<TestcontainersContainer>()
    .WithImage("postgres:latest")
    .WithWaitStrategy(Wait.ForUnixContainer()
        .UntilPortIsAvailable(5432)
        .WithTimeout(TimeSpan.FromMinutes(2)))
    .Build();

Porta Já em Uso

Sempre use mapeamento de porta aleatório:

.WithPortBinding(5432, true) // true = atribuir porta pública aleatória

Contêineres Não Limpos

Garanta a disposição adequada:

public async Task DisposeAsync()
{
    await _connection?.DisposeAsync();
    await _container?.DisposeAsync();
}

Testes Falham no CI Mas Passam Localmente

Garanta que o CI tenha suporte Docker:

# GitHub Actions
runs-on: ubuntu-latest # Tem Docker pré-instalado

Integração com CI/CD

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

Dicas de Desempenho

  1. Reutilize contêineres - Compartilhe fixtures entre testes de uma coleção
  2. Use Respawn - Reinicie os dados sem recriar contêineres
  3. Execução paralela - TestContainers lida com conflitos de porta automaticamente
  4. Use imagens leves - Versões Alpine são menores e mais rápidas
  5. Cache de imagens - O Docker armazenará as imagens baixadas localmente

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

Habilidades Relacionadas