Тестирование маршрутов POST и GET без параметров веб-API ASP.NET с помощью WebApiContrib.Testing

Я пытаюсь настроить некоторые тесты маршрута, используя библиотеку WebApiContrib.Testing. Мои тесты на получение (вроде этого) работают нормально...

    [Test]
    [Category("Auth Api Tests")]
    public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
    {
        "~/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>(c => c.Get("sjkfhiuehfkshjksdfh"));
    }

Я довольно потерян в пост-тесте - в настоящее время у меня есть следующее, которое не работает с NotImplementedException...

    [Test]
    [Category("Auth Api Tests")]
    public void TheAuthControllerAcceptsAPost()
    {
        "~/auth".ShouldMapTo<AuthController>(c => c.Post(new AuthenticationCredentialsModel()), "POST");
    }

Вот установка и разборка для полноты...

    [SetUp]
    public void SetUpTest()
    {
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        WebApiConfig.Register(GlobalConfiguration.Configuration);
    }

    [TearDown]
    public void TearDownTest()
    {
        RouteTable.Routes.Clear();
        GlobalConfiguration.Configuration.Routes.Clear();
    }

Маршрут, который я пытаюсь проверить, является маршрутом POST по умолчанию, который сопоставляется с этим вызовом метода...

    [AllowAnonymous]
    public HttpResponseMessage Post([FromBody] AuthenticationCredentialsModel model)
    { *** Some code here that doesn't really matter *** }

Я также получаю отказ в этом тесте, который проверяет стандартный маршрут GET без параметров, возвращающий все элементы...

    [Test]
    [Category("VersionInfo Api Tests")]
    public void TheVersionInfoControllerAcceptsAMultipleItemGetRouteForAllItems()
    {
        "~/versioninfo".ShouldMapTo<VersionInfoController>(c => c.Get());
    }

Который тестирует этот метод...

    public HttpResponseMessage Get()
    { *** Some code here that doesn't really matter *** }

Эта библиотека была рекомендована в нескольких статьях, которые я читал, но теперь я не уверен, делаю ли я что-то не так или она просто довольно ограничена, и мне лучше использовать свою собственную.


person Keith Jackson    schedule 22.08.2013    source источник


Ответы (2)


Кажется, здесь есть некоторые неприятные проблемы - после отладки через библиотеку WebApiContrib.Testing я обнаружил следующее...

В коде сопоставления маршрутов, используемом для собственных тестов библиотек, сопоставления маршрутов выглядят так...

        // GET /api/{resource}
        routes.MapHttpRoute(
            name: "Web API Get All",
            routeTemplate: "api/{controller}",
            defaults: new {action = "Get"},
            constraints: new {httpMethod = new HttpMethodConstraint("GET")}
            );

Мое отображение маршрута выглядит так, по сути, для одного и того же маршрута...

    config.Routes.MapHttpRoute("DefaultApiGet", "{controller}", 
        new { action = "Get" }, 
        new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });

Обратите внимание на другой синтаксис построения ограничений. Мой код сопоставления маршрутов не допускает строк. Возможно, это что-то изменилось в версии WebAPI с тех пор, как была написана библиотека, я не уверен, но кажется, что любой маршрут, который я пытаюсь протестировать с помощью HttpMethodConstraint, потерпит неудачу.

Я продолжаю исследовать это; Теперь у меня есть «почему», но еще нет «как исправить».

ОБНОВЛЕНИЕ: Маршрутизация WebAPI принимает две формы ограничений, одна из которых представляет собой просто строку (подробнее здесь — http://forums.asp.net/p/1777747/4904556.aspx/1?Re+).Система+Web+Http+Routing+IHttpRouteConstraint+vs+System+Web+Routing+IRouteConstraint). Я попытался изменить таблицу маршрутизации, чтобы посмотреть, будет ли это иметь какое-либо значение, но это не помогло — просто чтобы вы знали!

person Keith Jackson    schedule 30.08.2013

В конце концов я исправил это, написав свой собственный, после прочтения сообщения Whyleee по другому вопросу здесь - Проверка конфигурации маршрута в ASP.NET WebApi (WebApiContrib.Testing мне не помог)

Я объединил его сообщение с некоторыми синтаксически понравившимися мне элементами из библиотеки WebApiContrib.Testing, чтобы создать следующий вспомогательный класс.

Это позволяет мне писать действительно легкие тесты, подобные этому...

[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
{
    "http://api.siansplan.com/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>("Get", "hash");
}

[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsAPost()
{
    "http://api.siansplan.com/auth".ShouldMapTo<AuthController>("Post", HttpMethod.Post);
}

Вспомогательный класс выглядит так...

using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Hosting;
using System.Web.Http.Routing;

namespace SiansPlan.Api.Tests.Helpers
{
    public static class RoutingTestHelper
    {
        /// <summary>
        /// Routes the request.
        /// </summary>
        /// <param name="config">The config.</param>
        /// <param name="request">The request.</param>
        /// <returns>Inbformation about the route.</returns>
        public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)
        {
            // create context
            var controllerContext = new HttpControllerContext(config, new Mock<IHttpRouteData>().Object, request);

            // get route data
            var routeData = config.Routes.GetRouteData(request);
            RemoveOptionalRoutingParameters(routeData.Values);

            request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
            controllerContext.RouteData = routeData;

            // get controller type
            var controllerDescriptor = new DefaultHttpControllerSelector(config).SelectController(request);
            controllerContext.ControllerDescriptor = controllerDescriptor;

            // get action name
            var actionMapping = new ApiControllerActionSelector().SelectAction(controllerContext);

            var info = new RouteInfo(controllerDescriptor.ControllerType, actionMapping.ActionName);

            foreach (var param in actionMapping.GetParameters())
            {
                info.Parameters.Add(param.ParameterName);
            }

            return info;
        }

        #region | Extensions |

        /// <summary>
        /// Determines that a URL maps to a specified controller.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="fullDummyUrl">The full dummy URL.</param>
        /// <param name="action">The action.</param>
        /// <param name="parameterNames">The parameter names.</param>
        /// <returns></returns>
        public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, params string[] parameterNames)
        {
            return ShouldMapTo<TController>(fullDummyUrl, action, HttpMethod.Get, parameterNames);
        }

        /// <summary>
        /// Determines that a URL maps to a specified controller.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="fullDummyUrl">The full dummy URL.</param>
        /// <param name="action">The action.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="parameterNames">The parameter names.</param>
        /// <returns></returns>
        /// <exception cref="System.Exception"></exception>
        public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, HttpMethod httpMethod, params string[] parameterNames)
        {
            var request = new HttpRequestMessage(httpMethod, fullDummyUrl);
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);

            var route = RouteRequest(config, request);

            var controllerName = typeof(TController).Name;
            if (route.Controller.Name != controllerName)
                throw new Exception(String.Format("The specified route '{0}' does not match the expected controller '{1}'", fullDummyUrl, controllerName));

            if (route.Action.ToLowerInvariant() != action.ToLowerInvariant())
                throw new Exception(String.Format("The specified route '{0}' does not match the expected action '{1}'", fullDummyUrl, action));

            if (parameterNames.Any())
            {
                if (route.Parameters.Count != parameterNames.Count())
                    throw new Exception(
                        String.Format(
                            "The specified route '{0}' does not have the expected number of parameters - expected '{1}' but was '{2}'",
                            fullDummyUrl, parameterNames.Count(), route.Parameters.Count));

                foreach (var param in parameterNames)
                {
                    if (!route.Parameters.Contains(param))
                        throw new Exception(
                            String.Format("The specified route '{0}' does not contain the expected parameter '{1}'",
                                          fullDummyUrl, param));
                }
            }

            return true;
        }

        #endregion

        #region | Private Methods |

        /// <summary>
        /// Removes the optional routing parameters.
        /// </summary>
        /// <param name="routeValues">The route values.</param>
        private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValues)
        {
            var optionalParams = routeValues
                .Where(x => x.Value == RouteParameter.Optional)
                .Select(x => x.Key)
                .ToList();

            foreach (var key in optionalParams)
            {
                routeValues.Remove(key);
            }
        }

        #endregion
    }

    /// <summary>
    /// Route information
    /// </summary>
    public class RouteInfo
    {
        #region | Construction |

        /// <summary>
        /// Initializes a new instance of the <see cref="RouteInfo"/> class.
        /// </summary>
        /// <param name="controller">The controller.</param>
        /// <param name="action">The action.</param>
        public RouteInfo(Type controller, string action)
        {
            Controller = controller;
            Action = action;
            Parameters = new List<string>();
        }

        #endregion

        public Type Controller { get; private set; }
        public string Action { get; private set; }
        public List<string> Parameters { get; private set; }
    }
}
person Keith Jackson    schedule 30.08.2013