我有一个ASP.NET Core API,我为此编写了自定义中间件,以便可以处理异常并在一个地方编写日志。通过Kestrel进行调试并从浏览器或邮递员提交请求时,中间件可以按要求工作,但是在我的测试中,响应正文始终为空流。
下面是中间件类和我编写的测试context.Response.WriteAsync(result)似乎由于某种原因似乎无法刷新流,但我不知道为什么。有人能解释吗?
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using System.IO;
namespace APP.API.Middleware
{
public class ExceptionHandler
{
private readonly RequestDelegate request;
private readonly ILogger logger;
public ExceptionHandler(RequestDelegate request, ILogger<ExceptionHandler> logger)
{
this.request = request;
this.logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await request(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception ex)
{
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
logger.LogError(ex, "Fatal exception");
var result = JsonConvert.SerializeObject(new { error = ex.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
return context.Response.WriteAsync(result);
}
}
}
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace APP.Testing.Middleware
{
[TestClass]
public class ExceptionHandler
{
[TestMethod]
public void HandleException()
{
var exceptionHandler = new API.Middleware.ExceptionHandler((innerHttpContext) =>
{
throw new System.Exception("Test exception");
}, new NullLogger<API.Middleware.ExceptionHandler>());
var context = new DefaultHttpContext();
exceptionHandler.Invoke(context).Wait();
context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var text = reader.ReadToEnd();
}
}
}
答案 0 :(得分:3)
欢迎堆栈溢出!
您的响应正文为空,因为您正在写入NullStream
(不要与null
值混淆)。
“没有后备存储的Stream。使用Null将输出重定向到将不消耗任何操作系统资源的流。在Null上调用提供写操作的Stream方法时,该调用只是返回,并且没有数据Null还实现了Read方法,该方法在不读取数据的情况下返回零。 -Docs
Body
属性的HttpResponse
属性的默认值正是
NullStream
。在实际情况下,当HTTP请求到达时,NullStream
被替换为HttpResponseStream
。您将无法单独使用它,因为其可访问性级别已设置为internal
。
由于单元测试仅是模拟真实场景,因此您可以将NullStream
替换为所需的任何类型的流,例如MemoryStream
:
var exceptionHandler = new ExceptionHandler((innerHttpContext) =>
{
throw new Exception("Test exception");
}, new NullLogger<ExceptionHandler>());
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream(); // <== Replace the NullStream
await exceptionHandler.Invoke(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var text = reader.ReadToEnd();
别忘了在单元测试结束时添加一些断言。毕竟,您想执行一些检查,对吧?
Assert.IsFalse(string.IsNullOrEmpty(text));
如@nkosi所指出的,除非有充分的理由,否则应始终使用await
关键字调用异步方法:
await exceptionHandler.Invoke(context);
并用async
标记方法定义,并使其返回Task
:
public async Task HandleException()
那样您就可以避免使用deadlocks。
测试类的命名约定也值得一提(但不是必须的)。显然,您可以根据自己的喜好命名,但是请记住,当您的测试类与要测试的类具有相同的名称时,最终会导致不必要的名称歧义。当然,您可以使用名称空间来写全名(就像您所做的那样),但是由于我的懒惰性质,这实在太多了,因此我在测试类时使用了不同的名称,例如ExceptionHandlerTests
。