---
name: dotnet-testing-advanced-aspire-testing
description: |
.NET Aspire Testing 整合測試框架完整指南。
涵蓋 AppHost 專案設定、DistributedApplicationTestingBuilder、容器生命週期管理。
包含從 Testcontainers 遷移、多服務編排、Respawn 配置與時間可測試性設計。
triggers:
# 核心關鍵字
- aspire testing
- .NET Aspire
- DistributedApplicationTestingBuilder
- AppHost testing
- 分散式測試
# 技術術語
- AspireAppFixture
- IAsyncLifetime
- ContainerLifetime.Session
- WithWebHostBuilder
- PostgreSqlContainer AspireContainer
- RedisContainer Aspire
# 使用情境
- 雲原生測試
- 多服務整合
- AppHost 編排
- Aspire.Hosting.Testing
- WaitForPostgreSqlReadyAsync
- EnsureDatabaseExistsAsync
- Respawn DbAdapter.Postgres
license: MIT
metadata:
author: Kevin Tseng
version: "1.0.0"
tags: "aspire, distributed-testing, cloud-native, testcontainers, integration-testing"
---
# .NET Aspire Testing 整合測試框架
## 適用情境
當被要求執行以下任務時,請使用此技能:
- 為 .NET Aspire 分散式應用建立整合測試
- 從 Testcontainers 遷移到 .NET Aspire Testing
- 設定 AppHost 專案進行測試
- 使用 DistributedApplicationTestingBuilder 建立測試環境
- 需要測試多個服務間的互動(資料庫、快取、API 等)
- 建立雲原生 .NET 應用的整合測試架構
## 前置需求
- .NET 8 SDK 或更高版本
- Docker Desktop(WSL 2 或 Hyper-V)
- AppHost 專案(.NET Aspire 應用編排)
## 核心概念
### .NET Aspire Testing 定位
**.NET Aspire Testing 是封閉式整合測試框架**,專為分散式應用設計:
- 在測試中重現與正式環境相同的服務架構
- 使用真實容器而非模擬服務
- 自動管理容器生命週期
### AppHost 專案的必要性
使用 .NET Aspire Testing 必須建立 AppHost 專案:
- 定義完整的應用架構和容器編排
- 測試重用 AppHost 配置建立環境
- 沒有 AppHost 就無法使用 Aspire Testing
### 與 Testcontainers 的差異
| 特性 | .NET Aspire Testing | Testcontainers |
| -------- | ------------------- | -------------- |
| 設計目標 | 雲原生分散式應用 | 通用容器測試 |
| 配置方式 | AppHost 宣告式定義 | 程式碼手動配置 |
| 服務編排 | 自動處理 | 手動管理 |
| 學習曲線 | 較高 | 中等 |
| 適用場景 | 已用 Aspire 的專案 | 傳統 Web API |
## 專案結構
```text
MyApp/
├── src/
│ ├── MyApp.Api/ # WebApi 層
│ ├── MyApp.Application/ # 應用服務層
│ ├── MyApp.Domain/ # 領域模型
│ └── MyApp.Infrastructure/ # 基礎設施層
├── MyApp.AppHost/ # Aspire 編排專案 ⭐
│ ├── MyApp.AppHost.csproj
│ └── Program.cs
└── tests/
└── MyApp.Tests.Integration/ # Aspire Testing 整合測試
├── MyApp.Tests.Integration.csproj
├── Infrastructure/
│ ├── AspireAppFixture.cs
│ ├── IntegrationTestCollection.cs
│ ├── IntegrationTestBase.cs
│ └── DatabaseManager.cs
└── Controllers/
└── MyControllerTests.cs
```
## 必要套件
### AppHost 專案
```xml
Exe
net9.0
true
```
### 測試專案
```xml
net9.0
true
```
## 容器生命週期管理
使用 `ContainerLifetime.Session` 確保測試資源自動清理:
```csharp
var postgres = builder.AddPostgres("postgres")
.WithLifetime(ContainerLifetime.Session);
var redis = builder.AddRedis("redis")
.WithLifetime(ContainerLifetime.Session);
```
- **Session**:測試會話結束後自動清理(推薦)
- **Persistent**:容器持續運行,需手動清理
## 等待服務就緒
容器啟動與服務就緒是兩個階段,需要等待機制:
```csharp
private async Task WaitForPostgreSqlReadyAsync()
{
const int maxRetries = 30;
const int delayMs = 1000;
for (int i = 0; i < maxRetries; i++)
{
try
{
var connectionString = await GetConnectionStringAsync();
await using var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync();
return;
}
catch (Exception ex) when (i < maxRetries - 1)
{
await Task.Delay(delayMs);
}
}
throw new InvalidOperationException("PostgreSQL 服務未能就緒");
}
```
## 資料庫初始化
Aspire 啟動容器但不自動建立資料庫:
```csharp
private async Task EnsureDatabaseExistsAsync(string connectionString)
{
var builder = new NpgsqlConnectionStringBuilder(connectionString);
var databaseName = builder.Database;
builder.Database = "postgres"; // 連到預設資料庫
await using var connection = new NpgsqlConnection(builder.ToString());
await connection.OpenAsync();
var checkDbQuery = $"SELECT 1 FROM pg_database WHERE datname = '{databaseName}'";
var dbExists = await new NpgsqlCommand(checkDbQuery, connection).ExecuteScalarAsync();
if (dbExists == null)
{
await new NpgsqlCommand($"CREATE DATABASE \"{databaseName}\"", connection)
.ExecuteNonQueryAsync();
}
}
```
## Respawn 配置
使用 PostgreSQL 時必須指定適配器:
```csharp
_respawner = await Respawner.CreateAsync(connection, new RespawnerOptions
{
TablesToIgnore = new Table[] { "__EFMigrationsHistory" },
SchemasToInclude = new[] { "public" },
DbAdapter = DbAdapter.Postgres // 關鍵!
});
```
## Collection Fixture 最佳實踐
避免每個測試類別重複啟動容器:
```csharp
[CollectionDefinition("Integration Tests")]
public class IntegrationTestCollection : ICollectionFixture
{
}
[Collection("Integration Tests")]
public class MyControllerTests : IntegrationTestBase
{
public MyControllerTests(AspireAppFixture fixture) : base(fixture) { }
}
```
## 時間可測試性
使用 `TimeProvider` 抽象化時間依賴:
```csharp
// 服務實作
public class ProductService
{
private readonly TimeProvider _timeProvider;
public ProductService(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
}
public async Task CreateAsync(ProductCreateRequest request)
{
var now = _timeProvider.GetUtcNow();
var product = new Product
{
CreatedAt = now,
UpdatedAt = now
};
// ...
}
}
// DI 註冊
builder.Services.AddSingleton(TimeProvider.System);
```
## 選擇建議
### 選擇 .NET Aspire Testing
- 專案已使用 .NET Aspire
- 需要測試多服務互動
- 重視統一的開發測試體驗
- 雲原生應用架構
### 選擇 Testcontainers
- 傳統 .NET 專案
- 需要精細的容器控制
- 與非 .NET 服務整合
- 團隊對 Aspire 不熟悉
## 常見問題
### 端點配置衝突
不要手動配置已由 Aspire 自動處理的端點:
```csharp
// ❌ 錯誤:會造成衝突
builder.AddProject("my-api")
.WithHttpEndpoint(port: 8080, name: "http");
// ✅ 正確:讓 Aspire 自動處理
builder.AddProject("my-api")
.WithReference(postgresDb)
.WithReference(redis);
```
### Dapper 欄位映射
PostgreSQL snake_case 與 C# PascalCase 的映射:
```csharp
// 在 Program.cs 初始化
DapperTypeMapping.Initialize();
// 或使用 SQL 別名
const string sql = @"
SELECT id, name, price,
created_at AS CreatedAt,
updated_at AS UpdatedAt
FROM products";
```
## 參考資源
### 原始文章
本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:
- **Day 24 - .NET Aspire Testing 入門基礎介紹**
- 鐵人賽文章:https://ithelp.ithome.com.tw/articles/10377071
- 範例程式碼:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day24
- **Day 25 - .NET Aspire 整合測試實戰:從 Testcontainers 到 .NET Aspire Testing**
- 鐵人賽文章:https://ithelp.ithome.com.tw/articles/10377197
- 範例程式碼:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day25
### 官方文件
- [.NET Aspire 官方文件](https://learn.microsoft.com/dotnet/aspire/)
- [Aspire Testing 文件](https://learn.microsoft.com/dotnet/aspire/testing)
## 程式碼範例
請參考同目錄下的範例檔案:
- `templates/apphost-program.cs` - AppHost 編排定義
- `templates/aspire-app-fixture.cs` - 測試基礎設施
- `templates/integration-test-collection.cs` - Collection Fixture 設定
- `templates/integration-test-base.cs` - 測試基底類別
- `templates/database-manager.cs` - 資料庫管理員
- `templates/controller-tests.cs` - 控制器測試範例
- `templates/test-project.csproj` - 測試專案設定
- `templates/apphost-project.csproj` - AppHost 專案設定