コンテンツにスキップ

テストで AppHost を管理する

使用するテストフレームワークを選択してください

Aspire を使って機能テストや統合テストを書く際には、AppHost(/ja/get-started/app-host/) インスタンスを効率的に管理することが重要です。AppHost はアプリケーション全体の実行環境を表しており、作成や破棄にコストがかかる場合があります。本記事では、Aspire のテストにおいて AppHost インスタンスをどのように管理するかを解説します。

Aspire でテストを書く場合は、Aspire.Hosting.Testing NuGet パッケージを使用します。このパッケージには、テスト内で AppHost インスタンスを管理するためのいくつかのヘルパークラスが含まれています。

DistributedApplicationTestingBuilder クラスを使用する

Section titled “DistributedApplicationTestingBuilder クラスを使用する”

最初のテストを書くチュートリアル では、AppHost インスタンスを作成するために使用できる DistributedApplicationTestingBuilder クラスを紹介しました:

var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();

DistributedApplicationTestingBuilder.CreateAsync<T> メソッドは、AppHost プロジェクトの型をジェネリック パラメーターとして受け取り、AppHost インスタンスを作成します。このメソッドは各テストの開始時に実行されますが、テストスイートが大きくなるにつれて、AppHost インスタンスを一度だけ作成し、テスト間で共有した方が効率的です。

xUnit では、テストクラスに IAsyncLifetime インターフェイスを実装することで、AppHost インスタンスの非同期な初期化と破棄をサポートできます。InitializeAsync メソッドはテスト実行前に AppHost インスタンスを作成するために使用され、DisposeAsync メソッドはすべてのテスト完了後に AppHost を破棄します。

public class WebTests : IAsyncLifetime
{
private DistributedApplication _app;
public async Task InitializeAsync()
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
_app = await appHost.BuildAsync();
}
public async Task DisposeAsync() => await _app.DisposeAsync();
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// ここにテストコードを記述します
}
}

MSTest では、テストクラスの static メソッド に ClassInitializeAttributeClassCleanupAttribute を付与することで、AppHost インスタンスの初期化とクリーンアップを行います。ClassInitialize メソッドはテスト実行前に AppHost インスタンスを作成するために使用され、ClassCleanup メソッドはすべてのテスト完了後に AppHost インスタンスを破棄します。

[TestClass]
public class WebTests
{
private static DistributedApplication _app;
[ClassInitialize]
public static async Task ClassInitialize(TestContext context)
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
_app = await appHost.BuildAsync();
}
[ClassCleanup]
public static async Task ClassCleanup() => await _app.DisposeAsync();
[TestMethod]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// ここにテストコードを記述します
}
}

NUnit では、テストクラスのメソッドに OneTimeSetUp 属性と OneTimeTearDown 属性を使用して、AppHost インスタンスのセットアップと破棄を行います。OneTimeSetUp メソッドはテスト実行前に AppHost インスタンスを作成するために使用され、OneTimeTearDown メソッドはすべてのテストが完了した後に AppHost インスタンスを破棄します。

public class WebTests
{
private DistributedApplication _app;
[OneTimeSetUp]
public async Task OneTimeSetup()
{
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
_app = await appHost.BuildAsync();
}
[OneTimeTearDown]
public async Task OneTimeTearDown() => await _app.DisposeAsync();
[Test]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// ここにテストコードを記述します
}
}

テスト実行の開始時に AppHost をフィールドとして保持しておくことで、各テストから再作成することなく AppHost にアクセスできるようになり、テスト実行にかかる時間を短縮できます。そしてテスト実行が完了すると AppHost は破棄され、コンテナーなど、テスト実行中に作成されたすべてのリソースがクリーンアップされます。

AppHost では、args パラメーターを使って引数にアクセスできます。引数は .NET の構成システム にも渡されるため、この方法で多くの構成設定を上書きできます。次の例では、コマンドライン オプションとして指定することで ASP.NET Core ランタイム環境 を上書きしています:

var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyAppHost>(
[
"--environment=Testing"
]);

他の引数も AppHost の Program に渡して、AppHost 内で利用できます。次の例では、Postgres インスタンスにデータ ボリュームを追加するかどうかを制御するための引数を AppHost に渡しています。

AppHost の Program では、構成を使ってボリュームの有効/無効を切り替えています:

var postgres = builder.AddPostgres("postgres1");
if (builder.Configuration.GetValue("UseVolumes", true))
{
postgres.WithDataVolume();
}

テストコードでは、AppHost の args"UseVolumes=false" を渡します:

public async Task DisableVolumesFromTest()
{
// 引数でボリュームを無効化する:
using var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.TestingAppHost1_AppHost>(
[
"UseVolumes=false"
]);
// UseVolumes=false を渡しているため、コンテナーにはボリューム Annotatin が付与されない
var postgres = builder.Resources.Single(r => r.Name == "postgres1");
Assert.Empty(postgres.Annotations.OfType<ContainerMountAnnotation>());
}

DistributedApplicationFactory クラスを使用する

Section titled “DistributedApplicationFactory クラスを使用する”

DistributedApplicationTestingBuilder クラスは多くの場面で便利ですが、たとえば builder を作成する前にコードを実行したい、あるいは AppHost をビルドした後に処理を挟みたいなど、AppHost の起動フローをより細かく制御したいケースもあります。そうした場合は、DistributedApplicationFactory クラスを自分で実装します。これは DistributedApplicationTestingBuilder が内部的に利用している仕組みです。

public class TestingAspireAppHost()
: DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
// メソッドをオーバーライドして処理を追加します
}

コンストラクターには、AppHost プロジェクト参照の型をパラメーターとして渡します。必要に応じて、基盤となるホスト アプリケーション ビルダーに引数を渡すこともできます。これらの引数は AppHost の起動方法を制御し、AppHost.cs ファイル内で AppHost インスタンスを起動するために使われる args 変数にも値として渡されます。

DistributionApplicationFactory クラスには、AppHost の準備から作成までの流れの中でカスタム動作を差し込める複数のライフサイクル メソッドが用意されています。オーバーライド可能なメソッドは OnBuilderCreatingOnBuilderCreatedOnBuildingOnBuilt です。

たとえば OnBuilderCreating を使うと、AppHost の作成前(依存する Azure リソースがプロビジョニングされる前)に、Azure のサブスクリプションやリソース グループ情報などの構成を設定できます。これにより、テストが正しい Azure 環境を使って実行されるようにできます。

public class TestingAspireAppHost()
: DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
protected override void OnBuilderCreating(
DistributedApplicationOptions applicationOptions,
HostApplicationBuilderSettings hostOptions)
{
hostOptions.Configuration ??= new();
hostOptions.Configuration["environment"] = "Development";
hostOptions.Configuration["AZURE_SUBSCRIPTION_ID"] = "00000000-0000-0000-0000-000000000000";
hostOptions.Configuration["AZURE_RESOURCE_GROUP"] = "my-resource-group";
}
}

.NET の構成システムには優先順位があるため、この例では 環境変数が appsettings.jsonsecrets.json の設定よりも優先されます。

また、ライフサイクルの別の活用例として、AppHost が利用するサービス構成を変更するケースがあります。次の例では、OnBuilderCreated API をオーバーライドして HttpClient にレジリエンス(耐障害性)を追加しています:

protected override void OnBuilderCreated(
DistributedApplicationBuilder applicationBuilder)
{
applicationBuilder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
}