我有一个ErrorHandlingMiddleware类,它在多个项目中使用,因此位于共享库中(而不是在API中),我想为它编写一些测试。我已经使用TestServer(如本文所述)编写了一个通过的测试,证明了当端点没有抛出异常时,我的中间件可以工作,但是我在确定如何故意抛出异常以触发错误处理部分时遇到了麻烦。
我唯一找到的是这个问题,它和我想要的完全相反。
这是我的中间件代码- ErrorHandlingService这里的东西只是处理和格式化我所有的错误很好。
public ErrorHandlingMiddleware(RequestDelegate next, IErrorHandlingService errHandler)
{
_next = next;
_errHandler = errHandler;
}
public async Task Invoke(HttpContext context, IHostEnvironment env)
{
try
{
_errHandler.AddRequestInformation(Guid.NewGuid(), DateTime.Now, $"{context.Request.Method} {context.Request.Path}");
await _next(context);
}
catch (Exception ex)
{
_errHandler.AddError(new MyError()
{
Title = "An unknown error occurred during execution",
ErrorID = "---",
ErrorMessage = env.IsProduction() ? "" : ex.ToString(),
HttpCode = HttpStatusCode.InternalServerError
});
context.Response.StatusCode = (int)_errHandler.ErrorResponse.StatusCode;
await context.Response.WriteAsJsonAsync(_errHandler.ErrorResponse);
}
}
和我的通过测试,测试no异常是否被抛出。
public class ErrorHandlingMiddlewareTests
{
private async Task<IHost> GetHost()
{
return await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.Add(ServiceDescriptor.Scoped<IErrorHandlingService, ErrorHandlingService>());
})
.Configure(app =>
{
app.UseMiddleware<ErrorHandlingMiddleware>();
});
})
.StartAsync();
}
[Fact]
public async Task NewRequest_AddsRequestInformation()
{
// Arrange
using var host = await GetHost();
var requestRoute = "/route/to/endpoint";
var server = host.GetTestServer();
server.BaseAddress = new Uri("https://example.com/");
// Act
var context = await server.SendAsync(c => //throws a 404, I'm not testing for that
{
c.Request.Method = HttpMethods.Get;
c.Request.Path = requestRoute;
});
// Assert
var errorResponse = host.Services.GetService<IErrorHandlingService>().ErrorResponse;
errorResponse.ErrorContent.Request.Should().Be("GET " + requestRoute);
errorResponse.ErrorContent.TraceID.Should().NotBeEmpty();
}
}
那么我现在如何让测试服务器抛出一个可以被我的中间件捕获的异常呢?不到24小时我就解决了结果和往常一样,我只需要睡一觉。StriplingWarrior的评论给我指出了一个指南,虽然没有说任何我不知道的东西,但它确实促使我思考一个应用程序实际上是如何在。net中构建的,以及一个正常的API应该去哪里寻找控制器/路由。
长话短说,在对Startup.cs的工作原理和一些路由基础知识进行了一些尝试和错误研究之后,我是这样解决的:
private async Task<IHost> GetHost()
{
return await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddRouting(); // <- new line
services.Add(ServiceDescriptor.Scoped<IErrorHandlingService, ErrorHandlingService>());
})
.Configure(app =>
{
app.UseRouting(); // <- new line
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseEndpoints(endpoints => // <- new section
{
endpoints.MapGet("/hello", () => "Hello World!");
endpoints.MapGet("/err", () => { throw new Exception(); });
});
});
})
.StartAsync();
}
使用一些新端点的内联定义意味着我可以很容易地控制进出的内容,并且如果需要的话,可能会使响应更复杂。我想这是相当罕见的,你测试的东西在一个类库与本地TestServer,但我希望这是帮助下一个人谁没有一个启动类使用。
一种方法是用一个标志重载处理程序,并将其包装在DEBUG中,这样它就不会出现在生产构建中。在需要错误响应的测试中,将true作为第三个参数发送。
#if DEBUG
public async Task Invoke(HttpContext context, IHostEnvironment env, isErrorTest)
{
if (isErrorTest)
{
// Either build and respond with your error here, or possibly set something up on the server that would cause the normal handler to throw the error you want
_errHandler.AddError(new MyError()
{
// ...
});
// ... error response
}
// if not error test, call your normal handler
}
#endif