Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing route configuration in ASP.NET WebApi

I am trying to do some unit testing of my WebApi route configuration. I want to test that the route "/api/super" maps to the Get() method of my SuperController. I've setup the below test and am having a few issues.

public void GetTest() {     var url = "~/api/super";      var routeCollection = new HttpRouteCollection();     routeCollection.MapHttpRoute("DefaultApi", "api/{controller}/");      var httpConfig = new HttpConfiguration(routeCollection);     var request = new HttpRequestMessage(HttpMethod.Get, url);      // exception when url = "/api/super"     // can get around w/ setting url = "http://localhost/api/super"     var routeData = httpConfig.Routes.GetRouteData(request);     request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;      var controllerSelector = new DefaultHttpControllerSelector(httpConfig);      var controlleDescriptor = controllerSelector.SelectController(request);      var controllerContext =         new HttpControllerContext(httpConfig, routeData, request);     controllerContext.ControllerDescriptor = controlleDescriptor;      var selector = new ApiControllerActionSelector();     var actionDescriptor = selector.SelectAction(controllerContext);      Assert.AreEqual(typeof(SuperController),         controlleDescriptor.ControllerType);     Assert.IsTrue(actionDescriptor.ActionName == "Get"); } 

My first issue is that if I don't specify a fully qualified URL httpConfig.Routes.GetRouteData(request); throws a InvalidOperationException exception with a message of "This operation is not supported for a relative URI."

I'm obviously missing something with my stubbed configuration. I would prefer to use a relative URI as it does not seem reasonable to use a fully qualified URI for route testing.

My second issue with my configuration above is I am not testing my routes as configured in my RouteConfig but am instead using:

var routeCollection = new HttpRouteCollection(); routeCollection.MapHttpRoute("DefaultApi", "api/{controller}/"); 

How do I make use of the assigned RouteTable.Routes as configured in a typical Global.asax:

public class MvcApplication : HttpApplication {     protected void Application_Start()     {         // other startup stuff          RouteConfig.RegisterRoutes(RouteTable.Routes);     } }  public class RouteConfig {     public static void RegisterRoutes(RouteCollection routes)     {         // route configuration     } } 

Further what I have stubbed out above may not be the best test configuration. If there is a more streamlined approach I am all ears.

like image 885
ahsteele Avatar asked Aug 07 '12 18:08

ahsteele


People also ask

How do I specify a route in Web API?

The default route template for Web API is "api/{controller}/{id}". In this template, "api" is a literal path segment, and {controller} and {id} are placeholder variables. When the Web API framework receives an HTTP request, it tries to match the URI against one of the route templates in the routing table.

What is convention based routing in Web API?

What is Convention Based Routing in ASP.NET MVC? Routing is a mechanism which is used to handle the incoming requests coming from browsers and it represent the particular action rather than any static or physical files.


1 Answers

I was recently testing my Web API routes, and here is how I did that.

  1. First, I created a helper to move all Web API routing logic there:
    public static class WebApi     {         public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)         {             // create context             var controllerContext = new HttpControllerContext(config, Substitute.For<IHttpRouteData>(), 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);              return new RouteInfo             {                 Controller = controllerDescriptor.ControllerType,                 Action = actionMapping.ActionName             };         }          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);             }         }     }      public class RouteInfo     {         public Type Controller { get; set; }          public string Action { get; set; }     } 
  1. Assuming I have a separate class to register Web API routes (it is created by default in Visual Studio ASP.NET MVC 4 Web Application project, in the App_Start folder):
    public static class WebApiConfig     {         public static void Register(HttpConfiguration config)         {             config.Routes.MapHttpRoute(                 name: "DefaultApi",                 routeTemplate: "api/{controller}/{id}",                 defaults: new { id = RouteParameter.Optional }             );         }     } 
  1. I can test my routes easily:
    [Test]     public void GET_api_products_by_id_Should_route_to_ProductsController_Get_method()     {         // setups         var request = new HttpRequestMessage(HttpMethod.Get, "http://myshop.com/api/products/1");         var config = new HttpConfiguration();          // act         WebApiConfig.Register(config);         var route = WebApi.RouteRequest(config, request);          // asserts         route.Controller.Should().Be<ProductsController>();         route.Action.Should().Be("Get");     }      [Test]     public void GET_api_products_Should_route_to_ProductsController_GetAll_method()     {         // setups         var request = new HttpRequestMessage(HttpMethod.Get, "http://myshop.com/api/products");         var config = new HttpConfiguration();          // act         WebApiConfig.Register(config);         var route = WebApi.RouteRequest(config, request);          // asserts         route.Controller.Should().Be<ProductsController>();         route.Action.Should().Be("GetAll");     }      .... 

Some notes below:

  • Yes, I'm using absolute URLs. But I don't see any issues here, because these are fake URLs, I don't need to configure anything for them to work, and they representing real requests to our web services.
  • You don't need to copy you route mappings code to the tests, if they are configured in the separate class with HttpConfiguration dependency (like in the example above).
  • I'm using NUnit, NSubstitute and FluentAssertions in the above example, but of course it's an easy task to do the same with any other test frameworks.
like image 184
whyleee Avatar answered Oct 12 '22 07:10

whyleee