aspire-integration-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegration Testing with .NET Aspire + xUnit
使用.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
在以下场景中使用本技能:
- 为.NET Aspire应用编写集成测试
- 测试带有真实数据库连接的ASP.NET Core应用
- 验证分布式应用中的服务间通信
- 在容器中使用实际基础设施(SQL Server、Redis、消息队列)进行测试
- 将Playwright UI测试与Aspire编排的服务相结合
- 通过正确的服务发现和网络测试微服务
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
- advanced-patterns.md:端点发现、数据库测试、Playwright、条件配置、Respawn、服务通信、消息队列
- ci-and-tooling.md:CI/CD集成、自定义资源等待器、结合MCP的Aspire CLI
Core Principles
核心原则
- Real Dependencies - Use actual infrastructure (databases, caches) via Aspire, not mocks
- Dynamic Port Binding - Let Aspire assign ports dynamically () to avoid conflicts
127.0.0.1:0 - Fixture Lifecycle - Use for proper test fixture setup and teardown
IAsyncLifetime - 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
- 真实依赖项 - 通过Aspire使用实际基础设施(数据库、缓存),而非模拟对象
- 动态端口绑定 - 让Aspire动态分配端口()以避免冲突
127.0.0.1:0 - 夹具生命周期 - 使用实现正确的测试夹具设置和清理
IAsyncLifetime - 端点发现 - 绝不硬编码URL;在运行时从Aspire发现端点
- 并行隔离 - 使用xUnit集合控制测试并行化
- 健康检查 - 在运行测试前始终等待服务进入健康状态
High-Level Testing Architecture
高级测试架构
┌─────────────────┐ ┌──────────────────────┐
│ xUnit test file │──uses────────────►│ AspireFixture │
└─────────────────┘ │ (IAsyncLifetime) │
└──────────────────────┘
│
│ starts
▼
┌───────────────────────────┐
│ DistributedApplication │
│ (from AppHost) │
└───────────────────────────┘
│ exposes
▼
┌──────────────────────────────┐
│ Dynamic HTTP Endpoints │
└──────────────────────────────┘
│ consumed by
▼
┌─────────────────────────┐
│ HttpClient / Playwright│
└─────────────────────────┘┌─────────────────┐ ┌──────────────────────┐
│ xUnit test file │──uses────────────►│ AspireFixture │
└─────────────────┘ │ (IAsyncLifetime) │
└──────────────────────┘
│
│ starts
▼
┌───────────────────────────┐
│ DistributedApplication │
│ (from AppHost) │
└───────────────────────────┘
│ exposes
▼
┌──────────────────────────────┐
│ Dynamic HTTP Endpoints │
└──────────────────────────────┘
│ consumed by
▼
┌─────────────────────────┐
│ HttpClient / Playwright│
└─────────────────────────┘Required NuGet Packages
所需NuGet包
xml
<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>xml
<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:
csharp
// 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");
}
}当运行多个各自启动IHost的集成测试时,默认的.NET主机生成器会启用配置重载的文件监视器。这会在Linux上耗尽文件描述符限制。
在运行任何测试前,将以下代码添加到测试项目中:
csharp
// TestEnvironmentInitializer.cs
using System.Runtime.CompilerServices;
namespace YourApp.Tests;
internal static class TestEnvironmentInitializer
{
[ModuleInitializer]
internal static void Initialize()
{
// 禁用测试主机中的配置文件监视
// 防止Linux上的文件描述符耗尽(inotify监视限制)
Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE", "false");
}
}Pattern 1: Basic Aspire Test Fixture (Modern API)
模式1:基础Aspire测试夹具(现代API)
csharp
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();
}
}
}csharp
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
模式2:在测试中使用夹具
csharp
[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.
csharp
[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);
}
}有关端点发现、数据库测试、Playwright UI测试、条件资源配置、Respawn数据库重置、服务间通信和消息队列测试模式,请参阅advanced-patterns.md。
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 |
| 模式 | 适用场景 |
|---|---|
| 基础夹具 | 简单HTTP端点测试 |
| 端点发现 | 避免硬编码URL |
| 数据库测试 | 验证数据访问层 |
| Playwright集成 | 结合真实后端的完整UI测试 |
| 配置覆盖 | 测试专属设置 |
| 健康检查 | 确保服务已就绪 |
| 服务通信 | 测试分布式系统交互 |
| 消息队列测试 | 验证异步消息传递 |
Tricky / Non-Obvious Tips
疑难技巧
| Problem | Solution |
|---|---|
| Tests timeout immediately | Call |
| Port conflicts between tests | Use xUnit |
| Flaky tests due to timing | Implement proper health check polling instead of |
| Can't connect to SQL Server | Retrieve connection string dynamically via |
| Parallel tests interfere | Use |
| Aspire dashboard conflicts | Only one dashboard can run at a time; tests reuse the same instance |
| 问题 | 解决方案 |
|---|---|
| 测试立即超时 | 调用 |
| 测试间端口冲突 | 使用xUnit |
| 因时序问题导致测试不稳定 | 实现正确的健康检查轮询,而非使用 |
| 无法连接到SQL Server | 通过 |
| 并行测试相互干扰 | 使用 |
| Aspire仪表板冲突 | 同一时间只能运行一个仪表板;测试会复用同一实例 |
Best Practices
最佳实践
- Use - Ensures proper async initialization and cleanup
IAsyncLifetime - Share fixtures via collections - Reduces test execution time by reusing app instances
- Discover endpoints dynamically - Never hard-code localhost:5000 or similar
- Wait for health checks - Don't assume services are immediately ready
- Test with real dependencies - Aspire makes it easy to use real SQL, Redis, etc.
- Clean up resources - Always implement properly
DisposeAsync - Use meaningful test data - Seed databases with realistic test data
- Test failure scenarios - Verify error handling and resilience
- Keep tests isolated - Each test should be independent and order-agnostic
- 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.
- 使用- 确保正确的异步初始化和清理
IAsyncLifetime - 通过集合共享夹具 - 复用应用实例以减少测试执行时间
- 动态发现端点 - 绝不硬编码localhost:5000之类的地址
- 等待健康检查 - 不要假设服务会立即就绪
- 使用真实依赖项测试 - Aspire让使用真实SQL、Redis等变得简单
- 清理资源 - 始终正确实现
DisposeAsync - 使用有意义的测试数据 - 为数据库填充真实的测试数据
- 测试失败场景 - 验证错误处理和恢复能力
- 保持测试隔离 - 每个测试应独立且与执行顺序无关
- 监控测试执行时间 - 如果测试缓慢,考虑并行化
有关GitHub Actions设置、自定义资源等待器和Aspire CLI/MCP集成,请参阅ci-and-tooling.md。
Debugging Tips
调试技巧
- Run Aspire Dashboard - When tests fail, check the dashboard at
http://localhost:15888 - Use Aspire CLI with MCP - Let AI assistants query real application state
- Enable detailed logging - Set for more verbose output
ASPIRE_ALLOW_UNSECURED_TRANSPORT=true - Check container logs - Use to inspect container output
docker logs - Use breakpoints in fixtures - Debug fixture initialization to catch startup issues
- Verify resource names - Ensure resource names match between AppHost and tests
- 运行Aspire仪表板 - 测试失败时,访问查看仪表板
http://localhost:15888 - 结合MCP使用Aspire CLI - 让AI助手查询真实的应用状态
- 启用详细日志 - 设置以获取更详细的输出
ASPIRE_ALLOW_UNSECURED_TRANSPORT=true - 检查容器日志 - 使用检查容器输出
docker logs - 在夹具中使用断点 - 调试夹具初始化以捕获启动问题
- 验证资源名称 - 确保AppHost和测试中的资源名称匹配