MiniAPI的单元测试与asp.net web api的单元测试大体是相同的(毕竟都是asp.net core),只是在小细节上有一些差异,文章中会说到这点。
本文测试框架是XUnit,Mock框架是Moq,关于这两个框架和库的学习,这里就忽略了。
首先创建两个项目,API项目MiniAPI19UnitTest,UnitTest项目MiniAPI19UnitTestUT,如下:
MiniAPI19UnitTest
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项目
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来mock 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 host的类型,封装后以供测试程序调用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项目是用Top Level的方式来开发的,Program的访问修饰符是internal,虽然添加引用了MiniAPI19UnitTest项目,但Program是访问不到的,这里有两个解决方案,要么不用Top Level,如下面这样写代码:
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
<ItemGroup><InternalsVisibleTo Include="MiniAPI19UnitTestUT"/></ItemGroup>
用反射工具查看API项目结果如下,Main函数是Top-Level Entry Point方式,也看不到Program
这时,就可以开心地写自己的单元测试了。