Asp.Net Core 单元测试正确姿势 (2)

Middleware单元测试重点在于对委托 _next 的模拟

示例 HealthMiddleware:

public class HealthMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly string _healthPath = "/health"; public HealthMiddleware(RequestDelegate next, ILogger<HealthMiddleware> logger, IConfiguration configuration) { this._next = next; this._logger = logger; var healthPath = configuration["Consul:HealthPath"]; if (!string.IsNullOrEmpty(healthPath)) { this._healthPath = healthPath; } } public async Task Invoke(HttpContext httpContext) { if (httpContext.Request.Path == this._healthPath) { httpContext.Response.StatusCode = (int)HttpStatusCode.OK; await httpContext.Response.WriteAsync("I'm OK!"); } else await _next(httpContext); } }

单元测试:

public class HealthMiddlewareTest { private readonly Mock<ILogger<HealthMiddleware>> _mockLogger; private readonly Mock<IConfiguration> _mockConfiguration; private readonly string _healthPath = "/health"; private readonly HttpContext _httpContext; private readonly Mock<RequestDelegate> _mockNext; //middleware next public HealthMiddlewareTest() { this._mockConfiguration = new Mock<IConfiguration>(); this._mockConfiguration.SetupGet(c => c["Consul:HealthPath"]).Returns(_healthPath); this._mockLogger = new Mock<ILogger<HealthMiddleware>>(); this._mockLogger.Setup(_ => _.Log<object>(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())) .Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>( (logLevel, eventId, message, ex, fun) => { Console.WriteLine($"{logLevel}\n{eventId}\n{message}\n{message}"); }); this._httpContext = new DefaultHttpContext(); this._httpContext.Response.Body = new MemoryStream(); this._httpContext.Request.Path = this._healthPath; this._mockNext = new Mock<RequestDelegate>();//next 委托 Mock this._mockNext.Setup(_ => _(It.IsAny<HttpContext>())).Returns(async () => { await this._httpContext.Response.WriteAsync("Hello World!"); //模拟http请求最终输出 }); } [Fact] public async Task health_request_test() { var middleWare = new HealthMiddleware(this._mockNext.Object, this._mockLogger.Object, this._mockConfiguration.Object); await middleWare.Invoke(this._httpContext);//执行middleware this._httpContext.Response.Body.Seek(0, SeekOrigin.Begin); //获取监控检查请求获取到的response内容 var reader = new StreamReader(this._httpContext.Response.Body); var returnStrs = await reader.ReadToEndAsync(); Assert.Equal("I'm OK!", returnStrs);//断言健康检查api是否中间件拦截输出 "I'm OK!" } [Fact] public async Task general_request_test() { this._mockConfiguration.SetupGet(c => c["Consul:HealthPath"]).Returns("/api/values"); var middleWare = new HealthMiddleware(this._mockNext.Object, this._mockLogger.Object, this._mockConfiguration.Object); await middleWare.Invoke(this._httpContext); this._httpContext.Response.Body.Seek(0, SeekOrigin.Begin); var reader = new StreamReader(this._httpContext.Response.Body); var returnStrs = await reader.ReadToEndAsync(); Assert.Equal("Hello World!", returnStrs); //断言非健康检查请求api返回模拟 Hello World! } } 6. Mock HttpClient

HttpClient 中的 GetAsync、PostAsync 等方法底层实际都是通过HttpMessageHandler 调用 SendAsync 完成(见源码),所以在 Mock HttpClient 时,实际需要 Mock 的是 HttpMessageHandler 的 SendAsync 方法:

[Fact] public async Task get_async_test() { var responseContent = "Hello world!"; var mockHttpClient = this.BuildMockHttpClient("https://github.com/", responseContent); var response = await mockHttpClient.GetStringAsync("/api/values"); Assert.Equal(responseContent, response); } private HttpClient BuildMockHttpClient(string baseUrl, string responseStr) { var mockHttpMessageHandler = new Mock<HttpMessageHandler>(); mockHttpMessageHandler.Protected() .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>()).ReturnsAsync((HttpRequestMessage request, CancellationToken token) => { HttpResponseMessage response = new HttpResponseMessage(); response.Content = new StringContent(responseStr, Encoding.UTF8); return response; }); var mockHttpClient = new HttpClient(mockHttpMessageHandler.Object); mockHttpClient.BaseAddress = new Uri(baseUrl); return mockHttpClient; } 结语

几个问题:

CI/CD 流程中应该包含单元测试

例如在编写 Repository 层进行单元测试时,经常有同学会编写依赖于数据库数据的单元测试,这样并不利于随时随地的进行单元测试检查,如果将该流程放在CI/CD中,在代码的发布过程中通过单元测试可以检查代码逻辑的正确性,同时依赖于数据库的单元测试将不会通过(通常情况下,生产环境和开发环境隔离),变相迫使开发小伙伴通过 mock 方式模拟数据库返回结果。这个原则同样适用于不能依赖三方API编写单元测试。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zggspj.html