
Aspire Integration Testing
- 376 installs
- 1.1k repo stars
- Updated July 3, 2026
- aaronontheweb/dotnet-skills
aspire-integration-testing is a .NET agent skill that teaches writing xUnit integration tests for Aspire distributed applications using DistributedApplicationTestingBuilder, real containerized dependencies, and dynamic e
About
aspire-integration-testing is an agent skill from aaronontheweb/dotnet-skills that teaches robust integration testing for .NET Aspire distributed applications using xUnit. It centers on DistributedApplicationTestingBuilder from the Aspire.Hosting.Testing NuGet package, which launches the full AppHost with real infrastructure (SQL Server, Redis, message queues) in containers instead of mocks. Six core principles cover real dependencies, dynamic port binding (127.0.0.1:0), IAsyncLifetime fixture lifecycle, runtime endpoint discovery, xUnit collection parallelization, and health-check waits via ResourceNotifications. Advanced patterns include Playwright UI tests, Respawn database resets, conditional resource configuration, and CI/CD integration. Developers reach for this skill when testing microservice communication, ASP.NET Core apps with real databases, or Aspire-orchestrated multi-service deployments before release.
- Aspire test host setup
- Real dependency wiring
- Service graph assertions
- CI-friendly integration runs
- Regression detection
Aspire Integration Testing by the numbers
- 376 all-time installs (skills.sh)
- Ranked #662 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill aspire-integration-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 376 |
|---|---|
| repo stars | ★ 1.1k |
| Last updated | July 3, 2026 |
| Repository | aaronontheweb/dotnet-skills ↗ |
How do you integration test .NET Aspire apps?
Write and run Aspire-hosted integration tests that spin real dependencies, assert service wiring, and catch regressions before release.
Who is it for?
.NET developers writing closed-box integration tests for Aspire distributed applications with real SQL Server, Redis, or message-queue dependencies in containers.
Skip if: Single-project ASP.NET Core apps better served by WebApplicationFactory in-memory tests without full AppHost orchestration.
When should I use this skill?
User asks to integration test .NET Aspire apps, set up DistributedApplicationTestingBuilder fixtures, or test microservices with real containerized dependencies.
What you get
xUnit test fixtures, DistributedApplication test hosts, HttpClient helpers, and CI-ready Aspire integration test suites.
- xUnit integration test suites
- IAsyncLifetime test fixtures
- CI pipeline test configuration
By the numbers
- Six core testing principles: real deps, dynamic ports, fixtures, discovery, isolation, health
- Uses Aspire.Hosting.Testing NuGet package with DistributedApplicationTestingBuilder
Files
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
1. Real Dependencies - Use actual infrastructure (databases, caches) via Aspire, not mocks 2. Dynamic Port Binding - Let Aspire assign ports dynamically (127.0.0.1:0) to avoid conflicts 3. Fixture Lifecycle - Use IAsyncLifetime for proper test fixture setup and teardown 4. Endpoint Discovery - Never hard-code URLs; discover endpoints from Aspire at runtime 5. Parallel Isolation - Use xUnit collections to control test parallelization 6. 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:
// TestEnvironmentInitializer.cs
using System.Runtime.CompilerServices;
namespace YourApp.Tests;
internal static class TestEnvironmentInitializer
{
[ModuleInitializer]
internal static void Initialize()
{
// Disable config file watching in test hosts
// Prevents file descriptor exhaustion (inotify watch limit) on Linux
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, Playwright UI Tests, Conditional Resource Configuration, Respawn database reset, Service-to-Service Communication, and Message Queue testing patterns.
Common Patterns Summary
| Pattern | Use Case |
|---|---|
| Basic Fixture | Simple HTTP endpoint testing |
| Endpoint Discovery | Avoid hard-coded URLs |
| Database Testing | Verify data access layer |
| Playwright Integration | Full UI testing with real backend |
| Configuration Override | Test-specific settings |
| Health Checks | Ensure services are ready |
| Service Communication | Test distributed system interactions |
| Message Queue Testing | Verify async messaging |
Tricky / Non-Obvious Tips
| Problem | Solution |
|---|---|
| Tests timeout immediately | Call await _app.StartAsync() and wait for services to be healthy |
| Port conflicts between tests | Use xUnit CollectionDefinition to share fixtures |
| Flaky tests due to timing | Implement proper health check polling instead of Task.Delay() |
| Can't connect to SQL Server | Retrieve connection string dynamically via GetConnectionStringAsync() |
| Parallel tests interfere | Use [Collection] attribute to run related tests sequentially |
| Aspire dashboard conflicts | Only one dashboard can run at a time; tests reuse the same instance |
Best Practices
1. Use `IAsyncLifetime` - Ensures proper async initialization and cleanup 2. Share fixtures via collections - Reduces test execution time by reusing app instances 3. Discover endpoints dynamically - Never hard-code localhost:5000 or similar 4. Wait for health checks - Don't assume services are immediately ready 5. Test with real dependencies - Aspire makes it easy to use real SQL, Redis, etc. 6. Clean up resources - Always implement DisposeAsync properly 7. Use meaningful test data - Seed databases with realistic test data 8. Test failure scenarios - Verify error handling and resilience 9. Keep tests isolated - Each test should be independent and order-agnostic 10. Monitor test execution time - If tests are slow, consider parallelization
See ci-and-tooling.md for GitHub Actions setup, custom resource waiters, and Aspire CLI/MCP integration.
---
Debugging Tips
1. Run Aspire Dashboard - When tests fail, check the dashboard at http://localhost:15888 2. Use Aspire CLI with MCP - Let AI assistants query real application state 3. Enable detailed logging - Set ASPIRE_ALLOW_UNSECURED_TRANSPORT=true for more verbose output 4. Check container logs - Use docker logs to inspect container output 5. Use breakpoints in fixtures - Debug fixture initialization to catch startup issues 6. Verify resource names - Ensure resource names match between AppHost and tests
Advanced Testing Patterns
Extended patterns for Aspire integration testing including endpoint discovery, database testing, Playwright, conditional configuration, Respawn, and service communication.
Contents
- Endpoint Discovery
- Testing with Database Dependencies
- Combining with Playwright for UI Tests
- Conditional Resource Configuration for Tests
- Database Reset with Respawn
- Waiting for Resource Readiness
- Testing Service-to-Service Communication
- Testing with Message Queues
Endpoint Discovery
public static class DistributedApplicationExtensions
{
public static ResourceEndpoint GetEndpoint(
this DistributedApplication app,
string resourceName,
string? endpointName = null)
{
var resource = app.GetResource(resourceName);
if (resource is null)
throw new InvalidOperationException(
$"Resource '{resourceName}' not found");
var endpoint = endpointName is null
? resource.GetEndpoints().FirstOrDefault()
: resource.GetEndpoint(endpointName);
if (endpoint is null)
throw new InvalidOperationException(
$"Endpoint '{endpointName}' not found on resource '{resourceName}'");
return endpoint;
}
public static string GetEndpointUrl(
this DistributedApplication app,
string resourceName,
string? endpointName = null)
{
var endpoint = app.GetEndpoint(resourceName, endpointName);
return endpoint.Url;
}
}
// Usage in tests
[Fact]
public async Task CanAccessWebApplication()
{
var url = _fixture.App.GetEndpointUrl("yourapp");
var client = new HttpClient { BaseAddress = new Uri(url) };
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}Testing with Database Dependencies
public class DatabaseIntegrationTests
{
private readonly AspireAppFixture _fixture;
public DatabaseIntegrationTests(AspireAppFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task Database_ShouldBeInitialized()
{
// Get connection string from Aspire
var dbResource = _fixture.App.GetResource("yourdb");
var connectionString = await dbResource
.GetConnectionStringAsync();
// Test database access
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var result = await connection.QuerySingleAsync<int>(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES");
Assert.True(result > 0, "Database should have tables");
}
}Combining with Playwright for UI Tests
using Microsoft.Playwright;
public sealed class AspirePlaywrightFixture : IAsyncLifetime
{
private DistributedApplication? _app;
private IPlaywright? _playwright;
private IBrowser? _browser;
public DistributedApplication App => _app!;
public IBrowser Browser => _browser!;
public async Task InitializeAsync()
{
// Start Aspire application
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.YourApp_AppHost>();
_app = await appHost.BuildAsync();
await _app.StartAsync();
// Wait for app to be fully ready
await Task.Delay(2000); // Or use proper health check polling
// Start Playwright
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new()
{
Headless = true
});
}
public async Task DisposeAsync()
{
if (_browser is not null)
await _browser.DisposeAsync();
_playwright?.Dispose();
if (_app is not null)
await _app.DisposeAsync();
}
}
[Collection("Aspire Playwright collection")]
public class UIIntegrationTests
{
private readonly AspirePlaywrightFixture _fixture;
public UIIntegrationTests(AspirePlaywrightFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task HomePage_ShouldLoad()
{
var url = _fixture.App.GetEndpointUrl("yourapp");
var page = await _fixture.Browser.NewPageAsync();
await page.GotoAsync(url);
var title = await page.TitleAsync();
Assert.NotEmpty(title);
}
}Conditional Resource Configuration for Tests
Design your AppHost to support different configurations for interactive development (F5/CLI) vs automated test fixtures.
Default to production-like behavior in AppHost. Tests explicitly override what they need to be different.
Configuration Class in AppHost
public class AppHostConfiguration
{
public bool UseVolumes { get; set; } = true;
public string ExecutionMode { get; set; } = "Clustered";
public bool EnableTestAuth { get; set; } = false;
public bool UseFakeExternalServices { get; set; } = false;
public int Replicas { get; set; } = 1;
}AppHost Conditional Logic
var builder = DistributedApplication.CreateBuilder(args);
var config = builder.Configuration.GetSection("App")
.Get<AppHostConfiguration>() ?? new AppHostConfiguration();
var postgres = builder.AddPostgres("postgres").WithPgAdmin();
if (config.UseVolumes)
{
postgres.WithDataVolume();
}
var db = postgres.AddDatabase("appdb");
var migrations = builder.AddProject<Projects.YourApp_Migrations>("migrations")
.WaitFor(db)
.WithReference(db);
var api = builder.AddProject<Projects.YourApp_Api>("api")
.WaitForCompletion(migrations)
.WithReference(db)
.WithEnvironment("AkkaSettings__ExecutionMode", config.ExecutionMode)
.WithEnvironment("Testing__EnableTestAuth", config.EnableTestAuth.ToString())
.WithEnvironment("ExternalServices__UseFakes", config.UseFakeExternalServices.ToString());
if (config.Replicas > 1)
{
api.WithReplicas(config.Replicas);
}
builder.Build().Run();Test Fixture Overrides
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.YourApp_AppHost>([
"App:UseVolumes=false",
"App:ExecutionMode=LocalTest",
"App:EnableTestAuth=true",
"App:UseFakeExternalServices=true"
]);Common Conditional Settings
| Setting | F5/Development | Test Fixture | Purpose |
|---|---|---|---|
UseVolumes | true (persist data) | false (clean slate) | Database isolation |
ExecutionMode | Clustered (realistic) | LocalTest or Clustered | Actor system mode |
EnableTestAuth | false (use real OAuth) | true (/dev-login) | Bypass OAuth in tests |
UseFakeServices | false (real integrations) | true (no external calls) | External API isolation |
Replicas | 1 or more | 1 (simplicity) | Scale configuration |
Test Authentication Pattern
// In API startup, conditionally add test auth
if (builder.Configuration.GetValue<bool>("Testing:EnableTestAuth"))
{
app.MapPost("/dev-login", async (DevLoginRequest request, IAuthService auth) =>
{
var token = await auth.GenerateTokenAsync(request.UserId, request.Roles);
return Results.Ok(new { token });
});
}
// In tests
public async Task<string> LoginAsTestUser(string userId, string[] roles)
{
var response = await _httpClient.PostAsJsonAsync("/dev-login",
new { UserId = userId, Roles = roles });
var result = await response.Content.ReadFromJsonAsync<DevLoginResponse>();
return result!.Token;
}Fake External Services Pattern
public static IServiceCollection AddExternalServices(
this IServiceCollection services,
IConfiguration config)
{
if (config.GetValue<bool>("ExternalServices:UseFakes"))
{
services.AddSingleton<IEmailSender, FakeEmailSender>();
services.AddSingleton<IPaymentProcessor, FakePaymentProcessor>();
services.AddSingleton<IOAuthProvider, FakeOAuthProvider>();
}
else
{
services.AddSingleton<IEmailSender, SendGridEmailSender>();
services.AddSingleton<IPaymentProcessor, StripePaymentProcessor>();
services.AddSingleton<IOAuthProvider, Auth0Provider>();
}
return services;
}Database Reset with Respawn
For tests that modify data, use Respawn to reset between tests:
using Respawn;
public class AspireFixtureWithReset : IAsyncLifetime
{
private DistributedApplication? _app;
private Respawner? _respawner;
private string? _connectionString;
public async Task InitializeAsync()
{
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.YourApp_AppHost>([
"YourApp:UseVolumes=false"
]);
_app = await builder.BuildAsync();
await _app.StartAsync();
await _app.ResourceNotifications.WaitForResourceHealthyAsync("api");
var dbResource = _app.GetResource("appdb");
_connectionString = await dbResource.GetConnectionStringAsync();
_respawner = await Respawner.CreateAsync(_connectionString, new RespawnerOptions
{
TablesToIgnore = new[]
{
"__EFMigrationsHistory",
"schema_version",
"AspNetRoles"
},
DbAdapter = DbAdapter.Postgres
});
}
public async Task ResetDatabaseAsync()
{
if (_respawner is not null && _connectionString is not null)
{
await _respawner.ResetAsync(_connectionString);
}
}
public async Task DisposeAsync()
{
if (_app is not null)
await _app.DisposeAsync();
}
}Waiting for Resource Readiness
public static class ResourceExtensions
{
public static async Task WaitForHealthyAsync(
this DistributedApplication app,
string resourceName,
TimeSpan? timeout = null)
{
timeout ??= TimeSpan.FromSeconds(30);
var cts = new CancellationTokenSource(timeout.Value);
var resource = app.GetResource(resourceName);
while (!cts.Token.IsCancellationRequested)
{
try
{
var httpClient = app.CreateHttpClient(resourceName);
var response = await httpClient.GetAsync(
"/health",
cts.Token);
if (response.IsSuccessStatusCode)
return;
}
catch
{
// Resource not ready yet
}
await Task.Delay(500, cts.Token);
}
throw new TimeoutException(
$"Resource '{resourceName}' did not become healthy within {timeout}");
}
}Testing Service-to-Service Communication
[Fact]
public async Task WebApp_ShouldCallApi()
{
var webClient = _fixture.App.CreateHttpClient("webapp");
var apiClient = _fixture.App.CreateHttpClient("api");
// Verify API is accessible
var apiResponse = await apiClient.GetAsync("/api/data");
Assert.True(apiResponse.IsSuccessStatusCode);
// Verify WebApp calls API correctly
var webResponse = await webClient.GetAsync("/fetch-data");
Assert.True(webResponse.IsSuccessStatusCode);
var content = await webResponse.Content.ReadAsStringAsync();
Assert.NotEmpty(content);
}Testing with Message Queues
[Fact]
public async Task MessageQueue_ShouldProcessMessages()
{
var rabbitMqResource = _fixture.App.GetResource("messaging");
var connectionString = await rabbitMqResource
.GetConnectionStringAsync();
var factory = new ConnectionFactory
{
Uri = new Uri(connectionString)
};
using var connection = await factory.CreateConnectionAsync();
using var channel = await connection.CreateChannelAsync();
await channel.QueueDeclareAsync("test-queue", durable: false);
await channel.BasicPublishAsync(
exchange: "",
routingKey: "test-queue",
body: Encoding.UTF8.GetBytes("test message"));
// Wait for processing
await Task.Delay(1000);
// Verify message was processed
// (check database, file system, or other side effects)
}CI/CD and Tooling
CI/CD integration, advanced resource waiters, and Aspire CLI with MCP for AI-assisted development.
Contents
CI/CD Integration
GitHub Actions Example
name: Integration Tests
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
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: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release
- name: Run integration tests
run: |
dotnet test tests/YourApp.IntegrationTests \
--no-build \
-c Release \
--logger trx \
--collect:"XPlat Code Coverage"
- name: Publish test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: "**/TestResults/*.trx"Advanced Custom Resource Waiters
public static class ResourceWaiters
{
public static async Task WaitForSqlServerAsync(
this DistributedApplication app,
string resourceName,
CancellationToken ct = default)
{
var resource = app.GetResource(resourceName);
var connectionString = await resource.GetConnectionStringAsync(ct);
var retryCount = 0;
const int maxRetries = 30;
while (retryCount < maxRetries)
{
try
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(ct);
return; // Success!
}
catch (SqlException)
{
retryCount++;
await Task.Delay(1000, ct);
}
}
throw new TimeoutException(
$"SQL Server resource '{resourceName}' did not become ready");
}
public static async Task WaitForRedisAsync(
this DistributedApplication app,
string resourceName,
CancellationToken ct = default)
{
var resource = app.GetResource(resourceName);
var connectionString = await resource.GetConnectionStringAsync(ct);
var retryCount = 0;
const int maxRetries = 30;
while (retryCount < maxRetries)
{
try
{
var redis = await ConnectionMultiplexer.ConnectAsync(
connectionString);
await redis.GetDatabase().PingAsync();
return; // Success!
}
catch
{
retryCount++;
await Task.Delay(1000, ct);
}
}
throw new TimeoutException(
$"Redis resource '{resourceName}' did not become ready");
}
}
// Usage
public async Task InitializeAsync()
{
_app = await appHost.BuildAsync();
await _app.StartAsync();
// Wait for dependencies to be ready
await _app.WaitForSqlServerAsync("yourdb");
await _app.WaitForRedisAsync("cache");
}Aspire CLI and MCP Integration
Aspire 13.1+ includes MCP (Model Context Protocol) integration for AI coding assistants like Claude Code. This allows AI tools to query application state, view logs, and inspect traces.
Installing the Aspire CLI
# Install the Aspire CLI globally
dotnet tool install -g aspire.cli
# Or update existing installation
dotnet tool update -g aspire.cliInitializing MCP for Claude Code
# Navigate to your Aspire project
cd src/MyApp.AppHost
# Initialize MCP configuration (auto-detects Claude Code)
aspire mcp initThis creates the necessary configuration files for Claude Code to connect to your running Aspire application.
Running with MCP Enabled
# Run your Aspire app with MCP server
aspire run
# The CLI will output the MCP endpoint URL
# Claude Code can then connect and query:
# - Resource states and health status
# - Real-time console logs
# - Distributed traces
# - Available Aspire integrationsMCP Capabilities
When connected, AI assistants can:
- Query resources - Get resource states, endpoints, health status
- Debug with logs - Access real-time console output from all services
- Investigate telemetry - View structured logs and distributed traces
- Execute commands - Run resource-specific commands
- Discover integrations - List available Aspire hosting integrations (Redis, PostgreSQL, Azure services)
Benefits for Development
- AI assistants can see your actual running application state
- Debugging assistance uses real telemetry data
- No need for manual log copying/pasting
- AI can help correlate distributed trace spans
For more details, see:
Related skills
How it compares
Pick aspire-integration-testing for full Aspire AppHost closed-box tests; use WebApplicationFactory when testing a single ASP.NET Core project in isolation without distributed orchestration.
FAQ
What package powers Aspire integration tests?
aspire-integration-testing uses DistributedApplicationTestingBuilder from the Aspire.Hosting.Testing NuGet package to launch the full AppHost with real containerized dependencies for closed-box xUnit integration tests.
Should Aspire tests mock external dependencies?
aspire-integration-testing recommends real infrastructure via Aspire orchestration—SQL Server, Redis, and message queues in containers—rather than mocks, surfacing realistic networking and persistence issues before release.