测试自定义工厂中间件
Testing Custom Factory Middleware
我正在创建一个 asp.net 核心网络应用程序,它从用户定义的源(配置、数据库等)创建路由。我使用了一种基于中间件工厂的方法,效果很好,除了我不知道如何隔离中间件以进行 unit/integration 测试。我使用来自 HttpContext 的路由数据根据用户定义的配置源验证传入请求,因此我需要 HttpContext 具有 RouteData,这就是我倾向于使用 xUnit 进行集成测试的原因。
如果我使用通过 AspNetCore.TestHost.TestServer.CreateCLient() 创建的 httpclient 发出请求,我将通过我的整个调用链工作,这正是我所期望的。我需要做的是终止中间件或为 HttpContext 提供 RouteData。
我已经尝试使用 AutoFixture 和 DefaultHttpContext() 进行丑陋的单元测试,但正如预期的那样,我没有在我的 HttpContext 上获得任何 RouteData,所以我的测试永远无法通过。
中间件
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
if (_flowApis.Count == 0)
throw new ArgumentNullException();
var doesMatch = false;
var routeData = context.GetRouteData();
foreach (var api in _flowApis)
{
if (!RequestType.IsValidRequestType(api.Request.Type))
throw new InvalidOperationException();
else
foreach (Route route in routeData.Routers.Where(r => r.GetType() == typeof(Route)))
{
if (route.RouteTemplate == api.Route)
{
if (route.Constraints.TryGetValue("httpMethod", out IRouteConstraint routeConstraint))
{
var value = (HttpMethodRouteConstraint)routeConstraint;
if (value.AllowedMethods.Contains(api.Method))
{
doesMatch = true;
var routeValues = GetRouteValues(routeData);
var request = new Core.Request(context.Request, api, routeValues);
context.Items.Add(nameof(Core.Request), request);
break;
}
}
}
}
if (doesMatch)
break;
}
if (!doesMatch)
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("", new CancellationToken());
}
else
await next(context);
}
catch(ArgumentNullException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Flow Configuration in app.settings has not been set.", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
catch (InvalidOperationException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Invalid Request Type", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
}
单元测试
[TestMethod]
public async Task Request_path_matches_an_api_route_and_method()
{
var fixture = ScaffoldFixture();
var flowApi = fixture.Build<FlowApi>()
.With(p => p.Route, "/some/path")
.With(m => m.Method, "GET")
.Create();
var flowApiRequest = fixture.Build<Request>()
.With(t => t.Type, "CData")
.Create();
flowApi.Request = flowApiRequest;
var flowConfiguration = fixture.Create<FlowConfiguration>();
flowConfiguration.FlowApis.Clear();
flowConfiguration.FlowApis.Add(flowApi);
var middleware = new MatchRouteToFlowApiMiddleware(flowConfiguration: flowConfiguration);
var context = new DefaultHttpContext();
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));
context.Items.ContainsKey(nameof(Core.Request)).Should().BeTrue();
}
集成测试
[Fact]
public async Task Request_path_matches_an_api_route_and_method()
{
//Arrange
var response = await _httpClient.GetAsync("/some/route");
response.StatusCode.Should().Be(500);
}
执行单元测试时,我永远无法满足每个内部,因为我在 HttpContext 上没有路由数据。
执行集成测试时,中间件按预期执行,但由于它没有终止,我无法验证
我意识到尝试终止我的中间件只是为了使用 xUnit 验证它意味着我不会按原样测试我的系统,所以我更多地查看了我的单元测试设置。
我已经设法在 HttpContext 上获取路由数据。感觉很乱,但至少是一个起点。
private static DefaultHttpContext DefaultHttpContextWithRoute(Fixture fixture)
{
var context = new DefaultHttpContext();
var routeDictionary = new RouteValueDictionary
{
{ "some","path" }
};
context.Features.Set<IRoutingFeature>(new RoutingFeature());
context.Features.Get<IRoutingFeature>().RouteData = new RouteData(routeDictionary);
var inline = fixture.Create<DefaultInlineConstraintResolver>();
var route = new Route(new TestRouter(), "/some/path", inline);
var httpMethodRouteConstraint = new HttpMethodRouteConstraint("GET");
route.Constraints.Add("httpMethod", httpMethodRouteConstraint);
context.Features.Get<IRoutingFeature>().RouteData.Routers.Add(route);
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
return context;
}
private class TestRouter : IRouter
{
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
throw new NotImplementedException();
}
public Task RouteAsync(RouteContext context)
{
throw new NotImplementedException();
}
}
在每个单元测试中,我调用 DefaultHttpContext 并像这样调用我的中间件
DefaultHttpContext context = DefaultHttpContextWithRoute(fixture);
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));
我正在创建一个 asp.net 核心网络应用程序,它从用户定义的源(配置、数据库等)创建路由。我使用了一种基于中间件工厂的方法,效果很好,除了我不知道如何隔离中间件以进行 unit/integration 测试。我使用来自 HttpContext 的路由数据根据用户定义的配置源验证传入请求,因此我需要 HttpContext 具有 RouteData,这就是我倾向于使用 xUnit 进行集成测试的原因。
如果我使用通过 AspNetCore.TestHost.TestServer.CreateCLient() 创建的 httpclient 发出请求,我将通过我的整个调用链工作,这正是我所期望的。我需要做的是终止中间件或为 HttpContext 提供 RouteData。
我已经尝试使用 AutoFixture 和 DefaultHttpContext() 进行丑陋的单元测试,但正如预期的那样,我没有在我的 HttpContext 上获得任何 RouteData,所以我的测试永远无法通过。
中间件
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
if (_flowApis.Count == 0)
throw new ArgumentNullException();
var doesMatch = false;
var routeData = context.GetRouteData();
foreach (var api in _flowApis)
{
if (!RequestType.IsValidRequestType(api.Request.Type))
throw new InvalidOperationException();
else
foreach (Route route in routeData.Routers.Where(r => r.GetType() == typeof(Route)))
{
if (route.RouteTemplate == api.Route)
{
if (route.Constraints.TryGetValue("httpMethod", out IRouteConstraint routeConstraint))
{
var value = (HttpMethodRouteConstraint)routeConstraint;
if (value.AllowedMethods.Contains(api.Method))
{
doesMatch = true;
var routeValues = GetRouteValues(routeData);
var request = new Core.Request(context.Request, api, routeValues);
context.Items.Add(nameof(Core.Request), request);
break;
}
}
}
}
if (doesMatch)
break;
}
if (!doesMatch)
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("", new CancellationToken());
}
else
await next(context);
}
catch(ArgumentNullException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Flow Configuration in app.settings has not been set.", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
catch (InvalidOperationException ex)
{
var mex = new MiddleWareException<MatchRouteToFlowApiMiddleware>("Invalid Request Type", ex);
context.Response.StatusCode = 500;
await context.Response.WriteAsync("", new CancellationToken());
}
}
单元测试
[TestMethod]
public async Task Request_path_matches_an_api_route_and_method()
{
var fixture = ScaffoldFixture();
var flowApi = fixture.Build<FlowApi>()
.With(p => p.Route, "/some/path")
.With(m => m.Method, "GET")
.Create();
var flowApiRequest = fixture.Build<Request>()
.With(t => t.Type, "CData")
.Create();
flowApi.Request = flowApiRequest;
var flowConfiguration = fixture.Create<FlowConfiguration>();
flowConfiguration.FlowApis.Clear();
flowConfiguration.FlowApis.Add(flowApi);
var middleware = new MatchRouteToFlowApiMiddleware(flowConfiguration: flowConfiguration);
var context = new DefaultHttpContext();
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));
context.Items.ContainsKey(nameof(Core.Request)).Should().BeTrue();
}
集成测试
[Fact]
public async Task Request_path_matches_an_api_route_and_method()
{
//Arrange
var response = await _httpClient.GetAsync("/some/route");
response.StatusCode.Should().Be(500);
}
执行单元测试时,我永远无法满足每个内部,因为我在 HttpContext 上没有路由数据。
执行集成测试时,中间件按预期执行,但由于它没有终止,我无法验证
我意识到尝试终止我的中间件只是为了使用 xUnit 验证它意味着我不会按原样测试我的系统,所以我更多地查看了我的单元测试设置。
我已经设法在 HttpContext 上获取路由数据。感觉很乱,但至少是一个起点。
private static DefaultHttpContext DefaultHttpContextWithRoute(Fixture fixture)
{
var context = new DefaultHttpContext();
var routeDictionary = new RouteValueDictionary
{
{ "some","path" }
};
context.Features.Set<IRoutingFeature>(new RoutingFeature());
context.Features.Get<IRoutingFeature>().RouteData = new RouteData(routeDictionary);
var inline = fixture.Create<DefaultInlineConstraintResolver>();
var route = new Route(new TestRouter(), "/some/path", inline);
var httpMethodRouteConstraint = new HttpMethodRouteConstraint("GET");
route.Constraints.Add("httpMethod", httpMethodRouteConstraint);
context.Features.Get<IRoutingFeature>().RouteData.Routers.Add(route);
context.Request.Method = "GET";
context.Request.Path = "/some/path";
context.Response.Body = new MemoryStream();
return context;
}
private class TestRouter : IRouter
{
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
throw new NotImplementedException();
}
public Task RouteAsync(RouteContext context)
{
throw new NotImplementedException();
}
}
在每个单元测试中,我调用 DefaultHttpContext 并像这样调用我的中间件
DefaultHttpContext context = DefaultHttpContextWithRoute(fixture);
await middleware.InvokeAsync(context, (httpContext) => Task.FromResult(0));