.NET中间件单元测试:设置响应体后读取为空的问题
问题
我开发了一个用于修改响应的.NET中间件,正在对其进行单元测试。状态码和内容类型的断言都能成功,但断言响应体时,明明已经设置了响应体,读取到的却是空字符串。
我在另一项测试中修改和断言请求体时没有问题,想问能不能对响应体执行同样的操作?
中间件代码
using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace Web.API.Middleware { public class SampleMiddleware { private readonly RequestDelegate _next; public SampleMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { bool success = true; // 省略其他逻辑... context.Response.ContentType = MediaTypeNames.Text.Plain; context.Response.StatusCode = (int)HttpStatusCode.BadRequest; await context.Response.WriteAsync("Bad request"); // 此处设置响应体 success = false; if (success) { await _next(context); } } } }
单元测试代码
using System.IO; using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Web.API.Middleware; namespace Web.API.UnitTests { [TestClass] public class SampleMiddlewareTest { [TestMethod] public async Task BadRequestTest() { // Arrange var expected = "Bad request"; var httpContext = new DefaultHttpContext(); var nextMock = new Mock<RequestDelegate>(); var middleware = new SampleMiddleware(nextMock.Object); // Act await middleware.InvokeAsync(httpContext); // Assert Assert.AreEqual((int)HttpStatusCode.BadRequest, httpContext.Response.StatusCode); // 断言成功 Assert.AreEqual(MediaTypeNames.Text.Plain, httpContext.Response.ContentType); // 断言成功 StreamReader reader = new StreamReader(httpContext.Response.Body); var responseBody = reader.ReadToEnd(); Assert.AreEqual(expected, responseBody); // 断言失败 } } }
测试结果
Assert.AreEqual 失败。预期:<Bad request>。实际:<>.
解决方案
问题根源有两个:
- 响应流指针位置:调用
WriteAsync后,流的读写指针已经移动到了流的末尾,此时直接调用ReadToEnd会从末尾开始读取,自然返回空字符串。 - 默认流不可查找:
DefaultHttpContext的Response.Body默认是不可查找的管道流(PipeStream),无法重置指针位置。
只需两步修改测试代码即可解决:
修改后的测试代码
using System.IO; using System.Net; using System.Net.Mime; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Web.API.Middleware; namespace Web.API.UnitTests { [TestClass] public class SampleMiddlewareTest { [TestMethod] public async Task BadRequestTest() { // Arrange var expected = "Bad request"; var httpContext = new DefaultHttpContext(); // 将默认流替换为可查找的MemoryStream httpContext.Response.Body = new MemoryStream(); var nextMock = new Mock<RequestDelegate>(); var middleware = new SampleMiddleware(nextMock.Object); // Act await middleware.InvokeAsync(httpContext); // 将流指针重置到起始位置 httpContext.Response.Body.Seek(0, SeekOrigin.Begin); // Assert Assert.AreEqual((int)HttpStatusCode.BadRequest, httpContext.Response.StatusCode); Assert.AreEqual(MediaTypeNames.Text.Plain, httpContext.Response.ContentType); // 使用using确保流资源正确释放,同时用异步读取保持逻辑一致性 using var reader = new StreamReader(httpContext.Response.Body); var responseBody = await reader.ReadToEndAsync(); Assert.AreEqual(expected, responseBody); } } }
额外说明
- 用
using包裹StreamReader是最佳实践,能自动释放底层流资源。 - 优先使用异步方法
ReadToEndAsync,和中间件的异步执行逻辑保持一致,避免同步阻塞带来的潜在问题。 - 如果是更复杂的中间件场景(比如需要将流传递给后续中间件),可以考虑使用
Stream包装类来保留原始流的功能,但这个简单场景下替换为MemoryStream足够满足测试需求。
内容的提问来源于stack exchange,提问作者tariq
相关产品推荐
相关产品推荐

