Minimal API アプリでの単体テストと統合テスト

作成者: Fiyaz Bin HasanRick Anderson

統合テストの概要

統合テストでは 単体テストよりも広範なレベルでアプリのコンポーネントを評価します。 単体テストは、個々のクラス メソッドなど、分離されたソフトウェア コンポーネントをテストするために使用されます。 統合テストでは、2 つ以上のアプリ コンポーネントが、連携して予想される結果を生成することを確認します。これは、要求を完全に処理するために必要なすべてのコンポーネントを含む可能性があります。

これらの広範なテストは、アプリのインフラストラクチャとフレームワーク全体をテストするために使用されます。多くの場合、次のコンポーネントが含まれます。

  • データベース
  • ファイル システム
  • ネットワーク アプライアンス
  • 要求 - 応答パイプライン

単体テストでは、インフラストラクチャ コンポーネントの代わりに、フェイクまたはモック オブジェクトと呼ばれる、作成済みのコンポーネントを使用します。

単体テストと比較すると、統合テストは次のようになります。

  • アプリが運用環境で使用する実際のコンポーネントを使用します。
  • より多くのコードとデータ処理が必要です。
  • 実行に時間がかかります。

そのため、統合テストの使用は最も重要なインフラストラクチャ シナリオに限定します。 単体テストと統合テストのどちらを使用しても動作をテストできる場合は、単体テストを選択します。

統合テストの説明では、テスト対象のプロジェクトのことをよく "テスト対象システム"、または短縮して "SUT" と呼びます。 この記事全体を通して、テスト対象の ASP.NET Core アプリを指すために "SUT" を使用します。

データベースとファイル システムを使用するデータおよびファイル アクセスの "すべての順列として統合テストを記述してはいけません"。 通常は、アプリ全体でデータベースやファイル システムを操作する場所がいくつあったとしても、的を絞った一連の読み取り、書き込み、更新、削除の統合テストを行うことで、データベースおよびファイル システム コンポーネントを適切にテストすることができます。 これらのコンポーネントと連携するメソッドのロジックのルーチン テストには、単体テストを使用します。 単体テストでは、インフラストラクチャのフェイクまたはモックを使用することにより、テストの実行時間が短縮されます。

ASP.NET Core 統合テスト

ASP.NET Core の統合テストには、次のものが必要です。

  • テスト プロジェクトは、テストを格納して実行するために使用します。 テスト プロジェクトは SUT への参照を含みます。
  • テスト プロジェクトは、SUT のテスト Web ホストを作成し、テスト サーバー クライアントを使用して SUT との要求と応答を処理します。
  • テスト ランナーは、テストを実行し、テスト結果を報告するために使用されます。

統合テストでは、通常の Arrange (配置)Act (実行) 、および Assert (確認) のテスト ステップを含む一連のイベントに従います。

  1. SUT の Web ホストが構成されます。
  2. アプリに要求を送信するためのテスト サーバー クライアントが作成されます。
  3. Arrange (配置) テスト ステップが実行されます。テスト アプリが要求を準備します。
  4. Act (実行) テスト ステップが実行されます。クライアントは要求を送信し、応答を受信します。
  5. Assert (確認) テスト ステップが実行されます。実際の応答は、予測される応答に基づき、成功または失敗として検証されます。
  6. このプロセスは、すべてのテストが実行されるまで続行されます。
  7. テスト結果が報告されます。

通常、テスト Web ホストは、アプリの通常のテスト用の Web ホストとは異なる方法で構成されています。 たとえば、テスト用に別のデータベースまたは異なるアプリ設定を使用する場合があります。

テスト Web ホストやメモリ内テスト サーバー (TestServer) などのインフラストラクチャ コンポーネントは、Microsoft.AspNetCore.Mvc.Testing パッケージによって提供または管理されます。 このパッケージを使用すると、テストの作成と実行を効率化できます。

Microsoft.AspNetCore.Mvc.Testing パッケージは、次のタスクを処理します。

  • 依存関係ファイル ( .deps) を SUT からテスト プロジェクトの bin ディレクトリにコピーします。
  • テストを実行したときに、静的なファイルとページ/ビューが検出されるように、コンテンツ ルートを SUT のプロジェクト ルートに設定します。
  • WebApplicationFactory クラスを提供し、TestServer を使用して SUT のブートストラップを効率化します。

単体テストのドキュメントでは、テスト プロジェクトとテスト ランナーを設定する方法、テストを実行する方法の詳細な手順、テストおよびテスト クラスの命名方法に関する推奨事項について説明します。

単体テストを統合テストから分離し、異なるプロジェクトにします。 テストの分離は、次の面で役立ちます。

  • インフラストラクチャ テスト コンポーネントが誤って単体テストに含まれないようにすることができる。
  • 実行されるテストのセットを制御することができる。

GitHub のサンプル コードには、Minimal API アプリでの単体テストと統合テストの例が用意されています。

IResult 実装の種類

Microsoft.AspNetCore.Http.HttpResults 名前空間のパブリック IResult 実装の種類を使用すると、ラムダの代わりに名前付きメソッドを使用するときに、最小限のルート ハンドラーの単体テストを行うことができます。

次のコードでは、NotFound<TValue> クラスを使用します。

[Fact]
public async Task GetTodoReturnsNotFoundIfNotExists()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var notFoundResult = (NotFound) result.Result;

    Assert.NotNull(notFoundResult);
}

次のコードでは、Ok<TValue> クラスを使用します。

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title",
        Description = "Test description",
        IsDone = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var okResult = (Ok<Todo>)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

その他のリソース