Testing routes in ASP.NET Web API - StrathWeb

Strath

Testing routes in ASP.NET Web API

Because it can be really helpful

The question that often comes up when talking to developers and clients about Web API solutions is how exactly should you go about testing your route configuration in Web API? Some would perhaps argue that in certain cases, especially if you stick to RESTful approach, this type of testing wouldn’t even be necessary, because the convention over configuration provided by the framework means that you effecitvely end up testing something that’s internal working of Web API.

With that said, especially when you have complex routes, or when you break the Restful approach and provide RPC-style API, or if you have your API actions decorated with HTTP verbs that don’t match the action names, you probably want to (and probably should, if you ask me) test the API routing to make sure certain requests end up in proper places.

Let’s deal with this interesting problem.

Setting up the tests

Testing routing in Web API is relatively easy, but unfortunately, contrary to many other things Web API offers it’s not available out of the box and requires a little bit of helper code. Effecitvely what we need to do, is to utilize a bit of the framework’s APIs that’s used in its processing pipeline.

We will have to mock a few things, from the HTTP request that’s expected, through route data, to things like controller selector, action selector and controller context.

Because we will be using a few pieces that are interdependent on each other (probably because they were designed to be used i nthe processing pippeline rather than for test mocks), the easiest way is to create a helper class to wrap all that and expose just the meaningful public methods through that class.

This is our route testing class. What we want to be able to do is:
– pass it an instance of HttpConfiguration – this is where our routing is defined
– pass it an instance of HttpRequestMessage – we need it to encapsulate the url and HTTP method

Our testing class will expose 2 public method. First will return the Type of the controller determined for the given URL+Http method combination. The other will return the action name as string, again determined for the given URL+Http method combination.

I also already included a number of local fields which we will use later.

Building the Route Tester

Let’s go back to the constructor. It’s rather self explanatory what happens so far, as we just assign internally the paramteres passed by the caller. However, we must initialize some additional internal properties as well.

To explain why we do that, let’s look at the problem backwards. Ultimately, we want our class to be able to give us the Type of the controller [1] and name of the action [2]. To do that we need:
1) an instance of HttpControllerDescriptor
2) an instance of HttpActionDescriptor

To get [1], we need an instance of DefaultHttpControllerSelector which is why we init it in the constructor. To get [2] we’ll need an HttpControllerContext, so that’s also getting initialized in the constructor.

Now, for both of these to be used in proper context, we have to get the route data for the given request beforehand and set it as a property of the request, and that’s exactly what’s being done in the constructor as well.

Once that’s in place we can implement the public method. First the controller getter:

We use controller selector (if you recall, initialized in the class’ constructor) to select the proper controller for a given request (very important – that’s why we set the route data as property of the request, otherwise this would return null as there would be no routing context). We could then return the controller type straight away, but we set the ControllerDescriptor of the controllerContext field – this will be used later to determine the action that’s used, so let’s store it for that purpose. That’s one example of the spaghettish API we have to use to provide this route testing functionality, and why it’s better to lock this implementation in its own class.

The action getter is as follows:

Since it relies on the ControllerDescriptor to be set within the local controllerContext, we may need to go through the other method if that’s null in the beginning.

After that’s it’s straight forward, just return the action name through the action selector that’s relevant for the given controller context.

Usage

Now we can start using our testing class. I will use my favorite testing framework, xUnit, but obviously you can go with whichever suits you best.

In the test’s constructor (setup), we configure our HttpConfiguration, since it will be reusable between test methods.

Example test:

So we construct a simple HttpRequestMessage, passing to it an Http method to be used and the URL. Note, it needs to have athe host part of the Uri to be set to something – doesn’t matter what – but relative urls will throw 404 error.

Then we create an instance of our RouteTester and compare its public methods’ results with our expected outcome. I am using a reflection helper method to be able to pass method name in a strongly typed lambda manner, but you could hardcode it as a string as well. I just think such strongly typed approach is cleaner and more functional.

So, the helper code, for the record:

One more example test, this time for a POST method and a different API route:

Note that it’s just the coincidence that my controller’s in this test (and the models) are called UrlController and Url. That has nothing to do with routing, it’s just I pulled them from an application that’s a url-shortening service.

Now if you run the tests you can see them pass nicely.

Summary and source code

You can see that with a small class which hides this quite verbose and repetitive code required to set up route testing, it can actually be a pretty slick and easy to perform task. From my experience I can assure you that route testing is extremely valuable, especially combined with the strongly typed action comparison like the one I showed in the examples. If you have a lot of controllers and a lot of routes, you could very easily see how much your latest change affected the other parts of your application, and ultiamtely, that’s what testing is all about.

There is not much source code to go with this post to be honest, you could proabbly easily copy and paste stuff from the snippets, but if you want to have the source I added it to my “in memory” testing project on github, so please be welcome to grab that.

source code on GitHub

Be Sociable, Share!

  • Pingback: Dew Drop – August 13, 2012 (#1,383) | Alvin Ashcraft's Morning Dew

  • Pingback: Interesting .NET Links - August 14 , 2012 | TechBlog

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1168

  • Noctural Nath

    yep really like what youve done here

  • Sapan Desai

    Very nice and useful article. The only thing I would want to avoid here is to redefine my routes which we are doing here by invoking MapHttpRoute method. Instead, I would like to invoke RouteConfig.RegisterRoutes(). Trying with this but with no success so far.

    • Paul Manzotti

      Ah, I’ve finally managed to work this one out! There’s no direct way of casting between the different classes of Route that the Web API and HttpServer use, so you just loop over each route as populated by the RouteConfig method in your web api app.

      var config = new HttpConfiguration();
      System.Web.Http.HttpRouteCollection routes = config.Routes;

      System.Web.Routing.RouteCollection x = new System.Web.Routing.RouteCollection();
      RouteConfig.RegisterRoutes(x);

      int i = 0;
      foreach (var route in x)
      {
      var webRoute = (System.Web.Routing.Route)route;

      System.Web.Http.Routing.HttpRoute newRoute = new System.Web.Http.Routing.HttpRoute(webRoute.Url,
      new System.Web.Http.Routing.HttpRouteValueDictionary(webRoute.Defaults),
      new System.Web.Http.Routing.HttpRouteValueDictionary(webRoute.Constraints),
      new System.Web.Http.Routing.HttpRouteValueDictionary(webRoute.DataTokens));
      routes.Add(i++.ToString(), newRoute);
      }

      Server = new HttpServer(config);

      Hope that helps, and feel free to chip in if there’s a better way of doing this.

      • Suhas Chatekar

        Paul,

        Is that for loop in the end really needed? For me, it just worked. See my comment above.

        Thanks
        Suhas

  • Atachi

    Great work.

  • timber

    Thanks very nice blog!

  • Lobo Junior

    Very good. But, I prefer use MVCContrib.Tests to implements route tests. Because the framework already show the problem descriptions clearly.

    This post it’s amazing to explain the asp.net webapi internally behavior.

    Tks.

    • http://www.alexjamesbrown.com/ Alex Brown

      MVCContrib.Tests doesn’t work with Webi API controllers though?

  • http://www.mattwrock.com Matt Wrock

    Hi Filip,

    Thanks alot for posting this. This is the bestblog I have seen so far addressing the unit testing of WebApi Route configs.

    I work on the Work Item Tracking team on TFS at Microsoft and found this helpful for testing some routes for our upcoming rest controlers.

    Matt

    • Filip W

      Thanks Matt for this great comment! I really appreciate it!

  • Ben

    Hi Filip

    This is great, thanks for posting.

    I am attempting to test a WebApi controller that contains a number of GET methods, each with a different signature, which are mapped to differing URIs.
    Consider the following incoming URLs…

    api/companies/

    api/companies/1/

    api/companies?email=fred@asdf.com

    These map to the following methods in the CompaniesController respectively…

    public HttpResponseMessage Get()

    public HttpResponseMessage Get(int id)

    public HttpResponseMessage Get(string email)

    The route configuration is the default…

    config.Routes.MapHttpRoute(name: “DefaultApi”,routeTemplate: “api/{controller}/{id}”,defaults: new { id = RouteParameter.Optional });

    However, your unit test code fails when I initiate the request message and subsequent assert (shown below)…

    var request = new HttpRequestMessage(HttpMethod.Get, “http://localhost:41894/api/companies?email=fred@somewhere.com”);
    Assert.AreEqual(ReflectionHelpers.GetMethodName((CompaniesController c) => c.Get(“fred@somewhere.com”)),
    routeTesterConfiguration.GetActionName());

    The exception is…

    System.InvalidOperationException : Multiple actions were found that match the request:
    System.Net.Http.HttpResponseMessage Get(Int32) on type WebApi.Controllers.CompaniesController
    System.Net.Http.HttpResponseMessage Get(System.String) on type WebApi.Controllers.CompaniesController

    My three URIs route correctly when tested in Fiddler. Therefore, is it possible to modify your test code so that I can correctly route to different overloads of the same get method from different URIs as in my example?

    Many thanks

    Ben

    • Brian Watts

      I’m also gettting the “multiple actions were found…” InvalidOperationException for my Web API route tests where my ApiController actions receive various parameters from the querystring.

      Does anyone have a solution? Removing “OptionalParameters” does not solve anything for me.

  • MI VERTREES

    Just what I was looking for, Thank You

  • Raphael

    I just want to say I am just new to blogging and truly liked this blog site. More than likely I’m planning to bookmark your site . You definitely come with exceptional articles. Thanks for revealing your website.

  • Павел Неженцев

    Hi Filip. Great post and good way to test routes. But I stuck with a problem: not all routes which work in real mode (in browser, for example) work in unit tests.

    For example:
    GET “http://server/api/animals?id=1″ – works well, and routes to AnimalsController, Get(int id) method
    GET “http://server/api/animals/1″ – not working, routes to AnimalsController, GetAll() method

    So, only route values get from the query string are working. Not from the URL itself. Both routes work well in the browser. Example above is not a huge problem, actually, but I don’t know how to test URLs with inner collections. like:

    GET “http://server/api/animals/1/friends” – will not work, and I can’t change it to the query-string-way…

    Have you faced with this problem in your tests? Your help will be appreciated.

    • http://www.strathweb.com/ Filip W

      You can do the method signature checking using reflection (because WebAPI holds a collection of reflected action methods inside the action selector):

      public static MethodInfo GetMethodInfo(Expression<Func> expression)
      {
      var method = expression.Body as MethodCallExpression;
      if (method != null)
      return method.Method;

      throw new ArgumentException(“Expression is wrong”);
      }

      public static MethodInfo GetMethodInfo(Expression<Action> expression)
      {
      var method = expression.Body as MethodCallExpression;
      if (method != null)
      return method.Method;

      throw new ArgumentException(“Expression is wrong”);
      }

      Then in the route tester you can add

      public bool CompareSignatures(MethodInfo method)
      {
      if (_controllerContext.ControllerDescriptor == null)
      GetControllerType();

      var actionSelector = new ApiControllerActionSelector();
      var x = actionSelector.GetActionMapping(_controllerContext.ControllerDescriptor)[_request.Method.ToString()];

      return x.Any(item => ((MethodBase)(((ReflectedHttpActionDescriptor)item).MethodInfo)).ToString() == ((MethodBase)method).ToString());

      }

      And then in the tests:

      Assert.IsTrue(routeTester.CompareSignatures(ReflectionHelpers.GetMethodInfo((PersonController p) => p.Get())));
      Assert.IsTrue(routeTester.CompareSignatures(ReflectionHelpers.GetMethodInfo((PersonController p) => p.Get(new Guid(“d39a7401-5ec7-4611-9934-fdcc1be0dc98″)))));

    • http://www.strathweb.com/ Filip W

      This may not be readable – here is the code in the gist: https://gist.github.com/4527112

    • Павел Неженцев

      Thanks for the answer, Filip. But it seems we misunderstood each other. Anyway, I found the reason after some Web API framework debugging in the real mode and comparing results with the test mode. The problem was actually in route data:

      1) config.Routes.GetRouteData(request) returns all found route parameters, including optional ones. Web API framework is manually removing them before the data is used by action selector. Here is the simplified version of the method:

          private void RemoveOptionalRoutingParameters(IDictionary routeValues) {
      var optionalParams = routeValues
      .Where(x => x.Value == RouteParameter.Optional)
      .Select(x => x.Key)
      .ToList();

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

      And it’s called after getting the route data:

      var routeData = config.Routes.GetRouteData(request);
      RemoveOptionalRoutingParameters(routeData.Values);

      2) Route data should also be set to created controller context (it is used internally by action selector to get a correct action for request):

      controllerContext.RouteData = routeData;

      Hope this will help someone. Maybe it’s a good idea to add this to the post ;)

      • Nick Jensen

        This was extremely useful to me, thanks! It took me a while to realise
        that your RemoveOptionalRoutingParameters is necessary for my tests to
        distinguish between “/endpoint” and “/endpoint/{optionalParameter}”

  • Joanna T

    Thanks a lot for this post – very helpful! Now at least if I add a route I don’t have to manually check if all the other routings still work.

  • Pingback: Automated Testing of ASP.NET Web API and MVC applications | Robert Daniel Moore's Blog

  • Chris Bordeman

    This is a god send. Thanks!

  • http://www.alexjamesbrown.com/ Alex Brown

    I’m getting back a type of System.RuntimeType from GetControllerType() – so asserting on type is impossible.
    Any ideas?

  • Jerry

    Great article. Very helpful. Thanks.

  • Tom

    Thank you for this! I was bashing my head against a wall trying to find how I could write unit tests for Web API 2 attribute routing, and with just a couple of tweaks, this works!

    • http://www.strathweb.com/ Filip W

      thanks for the nice comment!

  • http://www.duanewingett.info/ Duane Wingett

    Brilliant article, I have tried to adapt this for normal controller routes, not WebApi routes, but I must have made a mistake and cannot see what it is. I am getting a 404 error on the line “var descriptor = _controllerSelector.SelectController(_request);” in the “GetControllerType()” method. I tried to test the “http://server/Home/Index” route.

    Any suggestions, please?

    • http://www.strathweb.com/ Filip W

      thanks!

      unfortunately MVC uses completely different action selection and controller matching process so this is not trivial to port to MVC.

      However, Anthony Steele has a library based on this post that reconciles both approaches https://github.com/AnthonySteele/MvcRouteTester so you should try that

      • http://www.duanewingett.info/ Duane Wingett

        Awesome, thank you. I have downloaded the example and will review it later. Thanks again for the tip.