Unit testing 在ASP.NET WebApi中测试路由配置

Unit testing 在ASP.NET WebApi中测试路由配置,unit-testing,routes,asp.net-web-api,Unit Testing,Routes,Asp.net Web Api,我正在尝试对我的路由配置进行一些单元测试。我想测试路由“/api/super”是否映射到我的超级控制器的Get()方法。我已经设置了下面的测试,并且有一些问题 public void GetTest() { var url=“~/api/super”; var routeCollection=新的HttpRouteCollection(); MapHttpRoute(“DefaultApi”,“api/{controller}/”; var httpConfig=新的HttpConfigurat

我正在尝试对我的路由配置进行一些单元测试。我想测试路由
“/api/super”
是否映射到我的
超级控制器的
Get()
方法。我已经设置了下面的测试,并且有一些问题

public void GetTest()
{
var url=“~/api/super”;
var routeCollection=新的HttpRouteCollection();
MapHttpRoute(“DefaultApi”,“api/{controller}/”;
var httpConfig=新的HttpConfiguration(routeCollection);
var request=newhttprequestmessage(HttpMethod.Get,url);
//url=“/api/super”时出现异常
//可以通过设置url=”四处走动http://localhost/api/super"
var routeData=httpConfig.Routes.GetRouteData(请求);
request.Properties[HttpPropertyKeys.HttpRouteDataKey]=路由数据;
var controllerSelector=新的默认HttpControllerSelector(httpConfig);
var controlleredescriptor=controllerSelector.SelectController(请求);
var控制器上下文=
新的HttpControllerContext(httpConfig、RoutedData、request);
controllerContext.ControllerDescriptor=ControllerDescriptor;
var选择器=新的ApiControllerActionSelector();
var actionDescriptor=selector.SelectAction(controllerContext);
Assert.AreEqual(类型化(超级控制器),
controlleDescriptor.ControllerType);
Assert.IsTrue(actionDescriptor.ActionName==“Get”);
}
我的第一个问题是,如果我没有指定一个完全限定的URL
httpConfig.Routes.GetRouteData(请求)
抛出一个
invalidoOperationException
异常,并显示一条消息“相对URI不支持此操作”

很明显,我的存根配置遗漏了一些东西。我更喜欢使用相对URI,因为在路由测试中使用完全限定的URI似乎不合理

我的上述配置的第二个问题是,我没有按照RouteConfig中的配置测试路由,而是使用:

var-routeCollection=new-HttpRouteCollection();
MapHttpRoute(“DefaultApi”,“api/{controller}/”;
如何使用在典型的Global.asax中配置的分配的
RouteTable.Routes

public类mvcapapplication:HttpApplication
{
受保护的无效应用程序\u Start()
{
//其他初创公司的东西
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
公共类路由图
{
公共静态无效注册表项(路由收集路由)
{
//路由配置
}
}

此外,我在上面指出的可能不是最好的测试配置。如果有更精简的方法,我洗耳恭听。

要从路由集合获取路由数据,在这种情况下,您确实需要提供完整的URI(只需使用“http://localhost/api/super").

要从RouteTable.routes测试路由,您可以执行以下操作:

var httpConfig = GlobalConfiguration.Configuration;
httpConfig.Routes.MapHttpRoute("DefaultApi", "api/{controller}/");

实际上,GlobalConfiguration将RouteTable.Routes修改为httpConfig.Routes。因此,当您将路由添加到httpConfig.Routes时,它实际上会添加到RouteTable.Routes。但要使其工作,您需要托管在ASP.NET中,以便填充诸如HostingEnvironment.ApplicationVirtualPath之类的环境设置。

我最近正在测试我的Web API路由,下面是我如何做到的

  • 首先,我创建了一个助手,将所有Web API路由逻辑移动到那里:
  • 公共静态类WebApi
    {
    公共静态路由信息路由请求(HttpConfiguration配置、HttpRequestMessage请求)
    {
    //创建上下文
    var controllerContext=new-HttpControllerContext(config,Substitute.For(),request);
    //获取路线数据
    var routeData=config.Routes.GetRouteData(请求);
    移除可选路由参数(路由数据值);
    request.Properties[HttpPropertyKeys.HttpRouteDataKey]=路由数据;
    controllerContext.RoutedData=RoutedData;
    //获取控制器类型
    var controllerDescriptor=新的默认HttpControllerSelector(配置)。SelectController(请求);
    controllerContext.ControllerDescriptor=ControllerDescriptor;
    //获取操作名称
    var actionMapping=new ApiControllerActionSelector().SelectAction(controllerContext);
    返回新路由信息
    {
    控制器=controllerDescriptor.ControllerType,
    Action=actionMapping.ActionName
    };
    }
    私有静态void RemoveOptionalRoutingParameters(IDictionary RouteValue)
    {
    var optionalParams=路由值
    .Where(x=>x.Value==RouteParameter.Optional)
    .选择(x=>x.Key)
    .ToList();
    foreach(可选参数中的var键)
    {
    路由值。移除(键);
    }
    }
    }
    公共类路由信息
    {
    公共类型控制器{get;set;}
    公共字符串操作{get;set;}
    }
    
  • 假设我有一个单独的类来注册Web API路由(默认情况下,它是在Visual Studio ASP.NET MVC 4 Web应用程序项目的App_Start文件夹中创建的):
  • 公共静态类WebApiConfig
    {
    公共静态无效寄存器(HttpConfiguration配置)
    {
    config.Routes.MapHttpRoute(
    名称:“DefaultApi”,
    routeTemplate:“api/{controller}/{id}”,
    默认值:新建{id=RouteParameter.Optional}
    );
    }
    }
    
  • 我可以轻松测试我的路线:
  • [测试]
    public void获取\u api\u产品\u按\u id获取\u应\u路由\u到\u产品控制器\u获取\u方法()
    {
    //设置
    var request=newhttprequestmessage(HttpMethod.Get)http://myshop.com/api/products/1");
    var config=新的HttpConfiguration();
    //表演
    
    [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; }
        }
    }
    
    [TestMethod]
    public void RouteToGetUser()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:4567/api/users/me");
    
        var config = new HttpConfiguration();
        WebApiConfig.Register(config);
        config.EnsureInitialized();
    
        var result = config.Routes.GetRouteData(request);
    
        Assert.AreEqual("api/{controller}/{id}", result.Route.RouteTemplate);
    }