$npx -y skills add Aaronontheweb/dotnet-skills --skill aspire-integration-testingWrite integration tests using .NET Aspire's testing facilities with xUnit. Covers test fixtures, distributed application setup, endpoint discovery, and patterns for testing ASP.NET Core apps with real dependencies.
| 1 | # Integration Testing with .NET Aspire + xUnit |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | |
| 5 | Use this skill when: |
| 6 | - Writing integration tests for .NET Aspire applications |
| 7 | - Testing ASP.NET Core apps with real database connections |
| 8 | - Verifying service-to-service communication in distributed applications |
| 9 | - Testing with actual infrastructure (SQL Server, Redis, message queues) in containers |
| 10 | - Combining Playwright UI tests with Aspire-orchestrated services |
| 11 | - Testing microservices with proper service discovery and networking |
| 12 | |
| 13 | ## Reference Files |
| 14 | |
| 15 | - [advanced-patterns.md](advanced-patterns.md): Endpoint discovery, database testing, Playwright, conditional config, Respawn, service communication, message queues |
| 16 | - [ci-and-tooling.md](ci-and-tooling.md): CI/CD integration, custom resource waiters, Aspire CLI with MCP |
| 17 | |
| 18 | ## Core Principles |
| 19 | |
| 20 | 1. **Real Dependencies** - Use actual infrastructure (databases, caches) via Aspire, not mocks |
| 21 | 2. **Dynamic Port Binding** - Let Aspire assign ports dynamically (`127.0.0.1:0`) to avoid conflicts |
| 22 | 3. **Fixture Lifecycle** - Use `IAsyncLifetime` for proper test fixture setup and teardown |
| 23 | 4. **Endpoint Discovery** - Never hard-code URLs; discover endpoints from Aspire at runtime |
| 24 | 5. **Parallel Isolation** - Use xUnit collections to control test parallelization |
| 25 | 6. **Health Checks** - Always wait for services to be healthy before running tests |
| 26 | |
| 27 | ## High-Level Testing Architecture |
| 28 | |
| 29 | ``` |
| 30 | ┌─────────────────┐ ┌──────────────────────┐ |
| 31 | │ xUnit test file │──uses────────────►│ AspireFixture │ |
| 32 | └─────────────────┘ │ (IAsyncLifetime) │ |
| 33 | └──────────────────────┘ |
| 34 | │ |
| 35 | │ starts |
| 36 | ▼ |
| 37 | ┌───────────────────────────┐ |
| 38 | │ DistributedApplication │ |
| 39 | │ (from AppHost) │ |
| 40 | └───────────────────────────┘ |
| 41 | │ exposes |
| 42 | ▼ |
| 43 | ┌──────────────────────────────┐ |
| 44 | │ Dynamic HTTP Endpoints │ |
| 45 | └──────────────────────────────┘ |
| 46 | │ consumed by |
| 47 | ▼ |
| 48 | ┌─────────────────────────┐ |
| 49 | │ HttpClient / Playwright│ |
| 50 | └─────────────────────────┘ |
| 51 | ``` |
| 52 | |
| 53 | ## Required NuGet Packages |
| 54 | |
| 55 | ```xml |
| 56 | <ItemGroup> |
| 57 | <PackageReference Include="Aspire.Hosting.Testing" Version="$(AspireVersion)" /> |
| 58 | <PackageReference Include="xunit" Version="*" /> |
| 59 | <PackageReference Include="xunit.runner.visualstudio" Version="*" /> |
| 60 | <PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" /> |
| 61 | </ItemGroup> |
| 62 | ``` |
| 63 | |
| 64 | ## CRITICAL: File Watcher Fix for Integration Tests |
| 65 | |
| 66 | 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. |
| 67 | |
| 68 | **Add this to your test project before any tests run:** |
| 69 | |
| 70 | ```csharp |
| 71 | // TestEnvironmentInitializer.cs |
| 72 | using System.Runtime.CompilerServices; |
| 73 | |
| 74 | namespace YourApp.Tests; |
| 75 | |
| 76 | internal static class TestEnvironmentInitializer |
| 77 | { |
| 78 | [ModuleInitializer] |
| 79 | internal static void Initialize() |
| 80 | { |
| 81 | // Disable config file watching in test hosts |
| 82 | // Prevents file descriptor exhaustion (inotify watch limit) on Linux |
| 83 | Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false"); |
| 84 | } |
| 85 | } |
| 86 | ``` |
| 87 | |
| 88 | ## Pattern 1: Basic Aspire Test Fixture (Modern API) |
| 89 | |
| 90 | ```csharp |
| 91 | using Aspire.Hosting; |
| 92 | using Aspire.Hosting.Testing; |
| 93 | |
| 94 | public sealed class AspireAppFixture : IAsyncLifetime |
| 95 | { |
| 96 | private DistributedApplication? _app; |
| 97 | |
| 98 | public DistributedApplication App => _app |
| 99 | ?? throw new InvalidOperationException("App not initialized"); |
| 100 | |
| 101 | public async Task InitializeAsync() |
| 102 | { |
| 103 | var builder = await DistributedApplicationTestingBuilder |
| 104 | .CreateAsync<Projects.YourApp_AppHost>([ |
| 105 | "YourApp:UseVolumes=false", |
| 106 | "YourApp:Environment=IntegrationTest", |
| 107 | "YourApp:Replicas=1" |
| 108 | ]); |
| 109 | |
| 110 | _app = await builder.BuildAsync(); |
| 111 | |
| 112 | using var startupCts = new CancellationTokenSource(TimeSpan.FromMinutes(10)); |
| 113 | await _app.StartAsync(startupCts.Token); |
| 114 | |
| 115 | using var healthCts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); |