xUnitで結合テストを書く（ASP.NET Core Web API）

xUnitでASP.NET CORE Web APIの結合テストを書くポイントをまとめ
想定としてはDBと繋がっているだけの単純なWebAPIが対象です

# テスト対象コード
簡単なTodo作成のControllerを用意してテストしていきます
データ保持用のリポジトリクラス(TodoRepository)も一緒に宣言

```diff_c_sharp
using Microsoft.AspNetCore.Mvc;

namespace xUnitSample;

public record TodoItem(int Id, string Content);

[Route("api/[controller]")]
[ApiController]
public class TodoController(TodoRepository repo) : ControllerBase
{
+   /// <summary>
+   /// Todoを作成する
+   /// </summary>
+   [HttpPost]
+   public ActionResult<TodoItem> Create([FromBody] string content)
+   {
+       var entity = repo.CreateItem(content);
+       return new TodoItem(entity.Id, entity.Content);
+   }
}

// 以下、データリポジトリの定義
public record TodoItemEntity(int Id, string Content);

public class TodoRepository
{
    private List<TodoItemEntity> _items = [];

    public TodoItemEntity CreateItem(string Content)
    {
        var id = _items.Any() ? _items.Max(x => x.Id) + 1 : 1;
        var item = new TodoItemEntity(id, Content);
        _items.Add(item);
        return item;
    }
}
```

結合テストとして実施するので、Controllerのメソッドを呼び出して後の処理もすべてモック化せずにテスト実行することになります
手順としては以下の2STEPのみです

1. テスト準備クラスの用意
1. テストコードの用意

# 1. テスト準備クラスの用意

早速テストコードを書いていきたいところですが、テストを実施する前準備のコードから用意していきます
xUnitでは`IClassFixture<T>`をテストクラスに実装することで、インスタンス化した際に一度だけ実施するセットアップ処理を行えますのでそれを使います
`IClassFixture<T>`の`T`にあたるクラスにセットアップ処理を書くことになります

ここではControllerやRepositoryを毎回初期化しないで済むように`IServiceProvider`を準備するコードを書いていきます

```diff_c_sharp
// IClassFixture<T>のTにあたるセットアップクラスを作成
public class TestFixture
{
    // テストクラスからControllerを取得するためのServiceProvider
    public IServiceProvider provider { get; private set; }

    public TestFixture()
    {
+       // ControllerとRepositoryをDIコンテナに登録
+       var services = new ServiceCollection();
+       services.AddTransient<xUnitSample.TodoRepository>();
+       services.AddSingleton<xUnitSample.TodoController>();
+       this.provider = services.BuildServiceProvider();
    }
}
```

:::note info
IDisposableを継承することでテスト終了後の後処理も可能です
テスト前後にDBをセットアップすることなども可能になりますね
:::

# 2. テストコードを用意する

前準備用ができたので実際にテストコードが書けます
`TodoController`の`Create`メソッドを対象に、指定した値でTodoを作成した際の戻り値を検証してみます

```diff_c_sharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace xUnitSample_Test;

+ // 「1. テスト準備クラス」の用意で用意したTestFixtureをIClassFixture<T>で使用
+ public class TodoTest(TestFixture fixture) : IClassFixture<TestFixture>
{
    [Fact]
    public void TodoItemが作成できること()
    {
        #region Arrange
+       // ServiceProviderからControllerを取得
+       var controller = fixture.provider.GetRequiredService<xUnitSample.TodoController>();
        #endregion Arrange

        #region Act
+       // "Test1"の内容でTodo登録
+       var result = controller.Create("Test1");
        #endregion Act

        #region Assert
        // 戻り値型がActionResult<TodoItem>であることを検証
+       var actionResult = Assert.IsType<ActionResult<xUnitSample.TodoItem>>(result);
+       var todoItem = Assert.IsType<xUnitSample.TodoItem>(actionResult.Value);

+       // IDとContentの検証
+       Assert.Equal(1, todoItem.Id);
+       Assert.Equal("Test1", todoItem.Content);
+       #endregion Assert
    }
}
```

# あとがき
以上で簡単に結合テストの作成でした
実際はDBの用意等必要ですが、その場合も`IClassFixture<T>`の内でInMemoryDBなどを指定することで少しの手間で実施できるかなと思います

Swagger等で確認するより手間はかかってしまいますが、一度つくったら流用して品質保証が楽になるので書いてしまったほうが長期的にはよいですね、、
テストコードを書く時間をなんとか捻出して安心安全な開発ライフを送りたいものです💯