Тестирование настраиваемого промежуточного программного обеспечения Factory

Я создаю основное веб-приложение asp.net, которое создает маршруты из определенных пользователем источников (конфигурация, база данных и т. Д.). Я использовал подход на основе фабрики промежуточного программного обеспечения, который отлично работает, за исключением того, что я не знаю, как изолировать промежуточное программное обеспечение для модульного / интеграционного тестирования. Я использую данные маршрута из HttpContext для проверки входящего запроса на соответствие определенному пользователем источнику конфигурации, поэтому мне нужно, чтобы HttpContext имел RouteData, поэтому я склоняюсь к тестированию интеграции с xUnit.

Если я делаю запрос, используя httpclient, созданный с помощью AspNetCore.TestHost.TestServer.CreateCLient (), я прохожу свой путь через всю цепочку вызовов, чего я и ожидал. Что мне нужно сделать, так это завершить работу промежуточного программного обеспечения или предоставить HttpContext с помощью RouteData.

Я пробовал уродливый модульный тест с использованием AutoFixture и DefaultHttpContext (), но, как и ожидалось, я не получаю RouteData в моем HttpContext, поэтому мои тесты никогда не пройдут.

ПО промежуточного слоя

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.

При выполнении интеграционного теста промежуточное ПО выполняется так, как ожидалось, но, поскольку оно не завершается, я не могу проверить


person SamV    schedule 03.09.2019    source источник


Ответы (1)


Я понял, что попытка завершить работу моего промежуточного программного обеспечения только для его проверки с помощью 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));
person SamV    schedule 03.09.2019