MiniAPI(二十九)：UnitTest

MiniAPIのユニットテストはasp.net web apiのユニットテストと大体同じです（どちらもasp.net coreですから）。しかし、細かな違いがいくつかあり、この記事で詳しく説明します。ここで使っているテストフレームワークはXUnit、モックフレームワークはMoqです。これら二つのフレームワークとライブラリについては、ここでは省略します。

まず、二つのプロジェクトを作成します。APIプロジェクトはMiniAPI19UnitTest、UnitTestプロジェクトはMiniAPI19UnitTestUTです。以下のようになります。

MiniAPI19UnitTest
```C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();

app.MapGet("/order", (IOrderService orderService) => {
    return "Result:" + orderService.GetOrder("123");
});
app.MapPost("/order", (Order order, IOrderService orderService) => {
    return "Result:" + orderService.AddOrder(order);
});
app.Run();

public interface IOrderService {
    bool AddOrder(Order order);
    string GetOrder(string orderNo);
}

public class OrderService : IOrderService {
    private readonly ILogger<OrderService> _logger;
    public OrderService(ILogger<OrderService> logger) {
        _logger = logger;
    }
    public string GetOrder(string orderNo) {
        return "this is my order,orderno:" + orderNo;
    }
    public bool AddOrder(Order order) {
        _logger.LogInformation(order.ToString());
        return true;
    }
}

public record Order {
    public string OrderNo { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
```

MiniAPI19UnitTestUT：このプロジェクトにMiniAPI19UnitTestプロジェクトの参照を追加します。
```C#
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace MiniAPI19UnitTestUT {
    public class MiniAPI19Test {
        [Fact]// 引数なしのテスト
        public async Task GetOrderTest() {
            var orderNo = "abcd";
            // Moqを使ってserverのインターフェースをモックし、層の隔離を実現
            var mock = new Mock<IOrderService>();
            mock.Setup(x => x.GetOrder(It.IsAny<string>())).Returns(orderNo);

            var myapp = new MyAppHostTest(services => services.AddSingleton(mock.Object));
            var client = myapp.CreateClient();
            var result = await client.GetStringAsync("/order");

            Assert.Equal($"Result:{orderNo}", result);
        }

        [Theory] // 引数ありのテスト
        [InlineData(true)]
        [InlineData(false)]
        public async Task PostOrderTest(bool backResult) {
            var mock = new Mock<IOrderService>();
            mock.Setup(x => x.AddOrder(It.IsAny<Order>())).Returns(backResult);

            var myapp = new MyAppHostTest(services => services.AddSingleton(mock.Object));
            var client = myapp.CreateClient();
            
            var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(new Order { OrderNo = "abcd", Name = "Surface Pro 8", Price = 10000 }), System.Text.Encoding.UTF8, "application/json");
            var response = await client.PostAsync("/order", content);
            var result = await response.Content.ReadAsStringAsync();

            Assert.Equal($"Result:{backResult}", result);
        }
    }

    // MiniAPI webホストのクラスをカプセル化し、テストプログラムで使用する
    class MyAppHostTest : WebApplicationFactory<Program> {
        private readonly Action<IServiceCollection> _services;
        public MyAppHostTest(Action<IServiceCollection> services) {
            _services = services;
        }
        protected override IHost CreateHost(IHostBuilder builder) {
            builder.ConfigureServices(_services);
            return base.CreateHost(builder);
        }
    }
}
```
上記のコードでは、Programクラスが見つからないというエラーが発生します。これはAPIプロジェクトがトップレベルの方式で開発されており、Programのアクセス修飾子がinternalであるためです。MiniAPI19UnitTestプロジェクトへの参照を追加しても、Programにはアクセスできません。解決策は二つあります。トップレベルを使用しない方法は以下のようになります：

```CSharp
public class Program {
    static void Main(string[] args) {
        var builder = WebApplication.CreateBuilder(args);
        builder.Services.AddScoped<IOrderService, OrderService>();
        var app = builder.Build();
        app.MapGet("/test", (IOrderService orderService) => {
            return "Result:" + orderService.GetOrder("123");
        });
        app.Run();
    }
}
```

または、MiniAPI19UnitTest.csprojファイルに以下の設定を追加し、テストプロジェクトがProgramにアクセスできるようにします。
```XML
<ItemGroup>
    <InternalsVisibleTo Include="MiniAPI19UnitTestUT"/>
</ItemGroup>
```
反射ツールを使ってAPIプロジェクトを確認すると、Main関数がトップレベルエントリポイント方式であり、Programが見えなくなっています。これで、自分のユニットテストを楽しく書くことができます。

(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247485040&idx=1&sn=c3d78501a15dce72b97080a37e8e666a&chksm=9f00595aa877d04cbce97e68a5f85c7de2f56817f32ee318e2e204bfc6c16b6b00fa1ec13ee2&token=732480897&lang=zh_CN#rd