Testes de Integração com .NET Aspire + xUnit
Quando Usar Esta Habilidade
Use esta habilidade quando:
- Escrevendo testes de integração para aplicações .NET Aspire
- Testando aplicativos ASP.NET Core com conexões reais a bancos de dados
- Verificando comunicação serviço a serviço em aplicações distribuídas
- Testando com infraestrutura real (SQL Server, Redis, filas de mensagens) em contêineres
- Combinando testes de UI com Playwright e serviços orquestrados pelo Aspire
- Testando microsserviços com descoberta de serviços e rede adequadas
Arquivos de Referência
- advanced-patterns.md: Descoberta de endpoints, testes de banco de dados, Playwright, configuração condicional, Respawn, comunicação entre serviços, filas de mensagens
- ci-and-tooling.md: Integração com CI/CD, espera personalizada por recursos, CLI do Aspire com MCP
Princípios Fundamentais
- Dependências Reais - Use infraestrutura real (bancos de dados, caches) via Aspire, não mocks
- Bind de Porta Dinâmico - Deixe o Aspire atribuir portas dinamicamente (
127.0.0.1:0) para evitar conflitos - Ciclo de Vida da Fixture - Use
IAsyncLifetimepara setup e teardown adequados da fixture de teste - Descoberta de Endpoints - Nunca hard-code URLs; descubra endpoints do Aspire em tempo de execução
- Isolamento Paralelo - Use coleções xUnit para controlar a paralelização dos testes
- Health Checks - Sempre aguarde os serviços ficarem saudáveis antes de executar os testes
Arquitetura de Alto Nível dos Testes
┌─────────────────┐ ┌──────────────────────┐
│ Arquivo de teste │──usa────────────►│ AspireFixture │
│ xUnit │ │ (IAsyncLifetime) │
└─────────────────┘ └──────────────────────┘
│
│ inicia
▼
┌───────────────────────────┐
│ DistributedApplication │
│ (do AppHost) │
└───────────────────────────┘
│ expõe
▼
┌──────────────────────────────┐
│ Endpoints HTTP Dinâmicos │
└──────────────────────────────┘
│ consumidos por
▼
┌─────────────────────────┐
│ HttpClient / Playwright│
└─────────────────────────┘
Pacotes NuGet Necessários
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="$(AspireVersion)" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
</ItemGroup>
CRÍTICO: Correção do File Watcher para Testes de Integração
Ao executar muitos testes de integração, cada um iniciando um IHost, o builder padrão do .NET habilita file watchers para recarregamento de configuração. Isso esgota os limites de descritores de arquivo no Linux.
Adicione isto ao seu projeto de teste antes de qualquer teste ser executado:
// TestEnvironmentInitializer.cs
using System.Runtime.CompilerServices;
namespace SeuApp.Tests;
internal static class TestEnvironmentInitializer
{
[ModuleInitializer]
internal static void Initialize()
{
// Desabilita o monitoramento de arquivos de configuração nos hosts de teste
// Previne a exaustão de descritores de arquivo (limite de watches inotify) no Linux
Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false");
}
}
Padrão 1: Fixture Básica para Teste Aspire (API Moderna)
using Aspire.Hosting;
using Aspire.Hosting.Testing;
public sealed class AspireAppFixture : IAsyncLifetime
{
private DistributedApplication? _app;
public DistributedApplication App => _app
?? throw new InvalidOperationException("App não inicializado");
public async Task InitializeAsync()
{
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.SeuApp_AppHost>([
"SeuApp:UseVolumes=false",
"SeuApp:Environment=IntegrationTest",
"SeuApp:Replicas=1"
]);
_app = await builder.BuildAsync();
using var startupCts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
await _app.StartAsync(startupCts.Token);
using var healthCts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
await _app.ResourceNotifications.WaitForResourceHealthyAsync("api", healthCts.Token);
}
public Uri GetEndpoint(string resourceName, string scheme = "https")
{
return _app?.GetEndpoint(resourceName, scheme)
?? throw new InvalidOperationException($"Endpoint para '{resourceName}' não encontrado");
}
public async Task DisposeAsync()
{
if (_app is not null)
{
await _app.DisposeAsync();
}
}
}
Padrão 2: Usando a Fixture nos Testes
[CollectionDefinition("Coleção Aspire")]
public class AspireCollection : ICollectionFixture<AspireAppFixture> { }
[Collection("Coleção Aspire")]
public class IntegrationTests
{
private readonly AspireAppFixture _fixture;
public IntegrationTests(AspireAppFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Aplicacao_DeveIniciar()
{
var httpClient = _fixture.App.CreateHttpClient("seuapp");
var response = await httpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Veja advanced-patterns.md para padrões de Descoberta de Endpoints, Testes de Banco de Dados, Testes de UI com Playwright, Configuração Condicional de Recursos, Reset de Banco de Dados com Respawn, Comunicação Serviço-a-Serviço e Testes de Filas de Mensagens.
Resumo de Padrões Comuns
| Padrão | Caso de Uso |
|---|---|
| Fixture Básica | Teste simples de endpoints HTTP |
| Descoberta de Endpoints | Evitar URLs hard-coded |
| Testes de Banco de Dados | Verificar camada de acesso a dados |
| Integração com Playwright | Teste completo de UI com backend real |
| Sobrescrita de Configuração | Configurações específicas de teste |
| Health Checks | Garantir que os serviços estão prontos |
| Comunicação entre Serviços | Testar interações em sistemas distribuídos |
| Testes de Filas de Mensagens | Verificar mensageria assíncrona |
Dicas Difíceis / Não-Óbvias
| Problema | Solução |
|---|---|
| Testes expiram imediatamente | Chame await _app.StartAsync() e aguarde os serviços ficarem saudáveis |
| Conflitos de porta entre testes | Use CollectionDefinition do xUnit para compartilhar fixtures |
| Testes instáveis devido ao tempo | Implemente polling adequado de health checks em vez de Task.Delay() |
| Não consigo conectar ao SQL Server | Recupere a string de conexão dinamicamente via GetConnectionStringAsync() |
| Testes paralelos interferem | Use o atributo [Collection] para executar testes relacionados sequencialmente |
| Conflitos de dashboard do Aspire | Apenas um dashboard pode ser executado por vez; os testes reutilizam a mesma instância |
Melhores Práticas
- Use
IAsyncLifetime- Garante inicialização e limpeza assíncronas adequadas - Compartilhe fixtures via coleções - Reduz o tempo de execução dos testes reutilizando instâncias de aplicação
- Descubra endpoints dinamicamente - Nunca hard-code localhost:5000 ou similar
- Aguarde health checks - Não presuma que os serviços estão imediatamente prontos
- Teste com dependências reais - O Aspire facilita o uso de SQL, Redis, etc. reais
- Limpe os recursos - Sempre implemente
DisposeAsynccorretamente - Use dados de teste significativos - Popule bancos de dados com dados de teste realistas
- Teste cenários de falha - Verifique tratamento de erros e resiliência
- Mantenha os testes isolados - Cada teste deve ser independente e independente de ordem
- Monitore o tempo de execução dos testes - Se os testes forem lentos, considere paralelização
Veja ci-and-tooling.md para configuração do GitHub Actions, espera personalizada por recursos e integração com Aspire CLI/MCP.
Dicas de Depuração
- Execute o Dashboard do Aspire - Quando os testes falharem, verifique o dashboard em
http://localhost:15888 - Use a CLI do Aspire com MCP - Permite que assistentes de IA consultem o estado real da aplicação
- Habilite logging detalhado - Defina
ASPIRE_ALLOW_UNSECURED_TRANSPORT=truepara saída mais verbosa - Verifique logs dos contêineres - Use
docker logspara inspecionar a saída dos contêineres - Use breakpoints nas fixtures - Depure a inicialização da fixture para capturar problemas de startup
- Verifique nomes dos recursos - Garanta que os nomes dos recursos coincidam entre o AppHost e os testes