关于 aspire-integration-testing
面向 .NET Aspire 开发者的 xUnit 集成测试技能指南,适用于 ASP.NET Core 应用和分布式服务。它说明如何使用真实基础设施、动态端点发现和健康检查来构建测试。
主要功能
- 01使用 Aspire 编排测试服务
- 02通过 IAsyncLifetime 管理夹具生命周期
- 03动态发现服务端点
- 04使用真实数据库与容器基础设施
- 05涵盖 Playwright 和消息队列测试
适用场景
- 01测试 ASP.NET Core 与数据库集成
- 02验证微服务间通信
- 03运行带真实后端的 UI 测试
MCP 相关
指南提及 Aspire CLI 与 MCP 集成,可让 AI 助手查询应用的实时状态。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill aspire-integration-testing安装后在 Claude Code 中输入/aspire-integration-testing确认已加载。
附带文件advanced-patterns.mdci-and-tooling.md
详细介绍
使用 .NET Aspire + xUnit 进行集成测试
何时使用此技能
在以下情况下使用此技能:
- 为 .NET Aspire 应用编写集成测试
- 使用真实数据库连接测试 ASP.NET Core 应用
- 验证分布式应用中的服务间通信
- 使用容器中的实际基础设施(SQL Server、Redis、消息队列)进行测试
- 将 Playwright UI 测试与 Aspire 编排的服务结合使用
- 使用适当的服务发现和网络配置测试微服务
参考文件
- advanced-patterns.md:端点发现、数据库测试、Playwright、条件配置、Respawn、服务通信、消息队列
- ci-and-tooling.md:CI/CD 集成、自定义资源等待器、结合 MCP 使用 Aspire CLI
核心原则
- 真实依赖项 - 通过 Aspire 使用实际基础设施(数据库、缓存),而非模拟对象
- 动态端口绑定 - 让 Aspire 动态分配端口(
127.0.0.1:0),以避免冲突 - 夹具生命周期 - 使用
IAsyncLifetime正确设置和清理测试夹具 - 端点发现 - 切勿硬编码 URL;在运行时从 Aspire 发现端点
- 并行隔离 - 使用 xUnit 集合控制测试并行化
- 运行状况检查 - 运行测试前始终等待服务就绪
测试架构概览
┌─────────────────┐ ┌──────────────────────┐
│ xUnit test file │──uses────────────►│ AspireFixture │
└─────────────────┘ │ (IAsyncLifetime) │
└──────────────────────┘
│
│ starts
▼
┌───────────────────────────┐
│ DistributedApplication │
│ (from AppHost) │
└───────────────────────────┘
│ exposes
▼
┌──────────────────────────────┐
│ Dynamic HTTP Endpoints │
└──────────────────────────────┘
│ consumed by
▼
┌─────────────────────────┐
│ HttpClient / Playwright│
└─────────────────────────┘
必需的 NuGet 包
<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>
重要:集成测试的文件监视器修复
运行许多分别启动 IHost 的集成测试时,默认的 .NET 主机构建器会启用配置重载的文件监视器。这会耗尽 Linux 上的文件描述符限制。
在运行任何测试之前,将以下内容添加到测试项目中:
// 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");
}
}
模式 1:基本 Aspire 测试夹具(现代 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();
}
}
}
模式 2:在测试中使用夹具
[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。
常见模式汇总
| 模式 | 用例 |
|---|---|
| 基本夹具 | 简单 HTTP 端点测试 |
| 端点发现 | 避免硬编码 URL |
| 数据库测试 | 验证数据访问层 |
| Playwright 集成 | 使用真实后端进行完整 UI 测试 |
| 配置覆盖 | 特定于测试的设置 |
| 运行状况检查 | 确保服务已就绪 |
| 服务通信 | 测试分布式系统交互 |
| 消息队列测试 | 验证异步消息传递 |
易错/不明显的提示
| 问题 | 解决方案 |
|---|---|
| 测试立即超时 | 调用 await _app.StartAsync() 并等待服务就绪 |
| 测试之间发生端口冲突 | 使用 xUnit CollectionDefinition 共享夹具 |
| 因时序问题导致测试不稳定 | 实现适当的运行状况检查轮询,而非使用 Task.Delay() |
| 无法连接到 SQL Server | 通过 GetConnectionStringAsync() 动态获取连接字符串 |
| 并行测试相互干扰 | 使用 [Collection] 特性按顺序运行相关测试 |
| Aspire 控制面板冲突 | 同一时间只能运行一个控制面板;测试会复用同一实例 |
最佳实践
- 使用
IAsyncLifetime- 确保正确地进行异步初始化和清理 - 通过集合共享固定装置 - 通过重复使用应用实例来缩短测试执行时间
- 动态发现端点 - 切勿硬编码 localhost:5000 或类似地址
- 等待健康检查 - 不要假设服务会立即就绪
- 使用真实依赖项进行测试 - Aspire 让使用真实的 SQL、Redis 等变得轻松
- 清理资源 - 始终正确实现
DisposeAsync - 使用有意义的测试数据 - 使用真实的测试数据为数据库填充初始数据
- 测试故障场景 - 验证错误处理和弹性
- 保持测试隔离 - 每项测试都应独立,且不依赖执行顺序
- 监控测试执行时间 - 如果测试速度较慢,可考虑并行化
请参阅 ci-and-tooling.md,了解 GitHub Actions 设置、自定义资源等待器以及 Aspire CLI/MCP 集成。
调试技巧
- 运行 Aspire Dashboard - 测试失败时,请检查
http://localhost:15888上的仪表板 - 将 Aspire CLI 与 MCP 配合使用 - 让 AI 助手查询实际应用状态
- 启用详细日志记录 - 设置
ASPIRE_ALLOW_UNSECURED_TRANSPORT=true以获取更详细的输出 - 检查容器日志 - 使用
docker logs检查容器输出 - 在固定装置中使用断点 - 调试固定装置初始化,以发现启动问题
- 验证资源名称 - 确保 AppHost 与测试中的资源名称一致
评论
支持 Markdown