Integration Testing with .NET Aspire + xUnit
When to Use This Skill
Use this skill when:
- Writing integration tests for .NET Aspire applications
- Testing ASP.NET Core apps with real database connections
- Verifying service-to-service communication in distributed applications
- Testing with actual infrastructure (SQL Server, Redis, message queues) in containers
- Combining Playwright UI tests with Aspire-orchestrated services
- Testing microservices with proper service discovery and networking
Reference Files
- advanced-patterns.md: Endpoint discovery, database testing, Playwright, conditional config, Respawn, service communication, message queues
- ci-and-tooling.md: CI/CD integration, custom resource waiters, Aspire CLI with MCP
Core Principles
- Real Dependencies - Use actual infrastructure (databases, caches) via Aspire, not mocks
- Dynamic Port Binding - Let Aspire assign ports dynamically (
127.0.0.1:0) to avoid conflicts
- Fixture Lifecycle - Use
IAsyncLifetime for proper test fixture setup and teardown
- Endpoint Discovery - Never hard-code URLs; discover endpoints from Aspire at runtime
- Parallel Isolation - Use xUnit collections to control test parallelization
- Health Checks - Always wait for services to be healthy before running tests
High-Level Testing Architecture
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ xUnit test file โโโusesโโโโโโโโโโโโโบโ AspireFixture โ
โโโโโโโโโโโโโโโโโโโ โ (IAsyncLifetime) โ
โโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ starts
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DistributedApplication โ
โ (from AppHost) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ exposes
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Dynamic HTTP Endpoints โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ consumed by
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HttpClient / Playwrightโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
Required NuGet Packages
<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>
CRITICAL: File Watcher Fix for Integration Tests
When running many integration tests that each start an IHost, the default .NET host builder enables file watchers for configuration reload. This exhausts file descriptor limits on Linux.
Add this to your test project before any tests run:
using System.Runtime.CompilerServices;
namespace YourApp.Tests;
internal static class TestEnvironmentInitializer
{
[ModuleInitializer]
internal static void Initialize()
{
Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false");
}
}
Pattern 1: Basic Aspire Test Fixture (Modern API)
using Aspire.Hosting;
using Aspire.Hosting.Testing;
public sealed class AspireAppFixture : IAsyncLifetime
{
private DistributedApplication? _app;
public DistributedApplication App => _app
?? throw new InvalidOperationException("App not initialized");
public async Task InitializeAsync()
{
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.YourApp_AppHost>([
"YourApp:UseVolumes=false",
"YourApp:Environment=IntegrationTest",
"YourApp: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 for '{resourceName}' not found");
}
public async Task DisposeAsync()
{
if (_app is not null)
{
await _app.DisposeAsync();
}
}
}
Pattern 2: Using the Fixture in Tests
[CollectionDefinition("Aspire collection")]
public class AspireCollection : ICollectionFixture<AspireAppFixture> { }
[Collection("Aspire collection")]
public class IntegrationTests
{
private readonly AspireAppFixture _fixture;
public IntegrationTests(AspireAppFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Application_ShouldStart()
{
var httpClient = _fixture.App.CreateHttpClient("yourapp");
var response = await httpClient.GetAsync("/");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
See advanced-patterns.md for Endpoint Discovery, Database Testing, Playwrig