Saturday, October 26, 2013

Fluent controller builder for unit testing Web API controllers

This post is not about unit testing Web API controllers. A gentleman named Peter Provost has already written about this in enough details here. I actually followed his post to fix some issues with my unit tests. In order to unit test Web API controllers you need to set up a few things before you can start calling action methods.


  1. Populate Request property of controller with a new instance of HttpRequestMessageClass
  2. Define a route
  3. Create a route data collection
  4. Create an instance of HttpControllerContext using the above and use this to set ControllerContext property on our controller instance
  5. Tell the request object created in first step to use the above configuration.    
Does this sound complex? It does to me, so let me show you the code that actually does the above and you would know that this only sounds complex
private static void SetupControllerForTests(ApiController controller)
{
    var config = new HttpConfiguration();
    var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/products");
    var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });

    controller.ControllerContext = new HttpControllerContext(config, routeData, request);
    controller.Request = request;
    controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
}

This code is not very complex to understand but it has lot of moving parts. For example, I may not always test for a post request or I may not always use the same route. These things change based on the context of you test. So reusing this code in multiple tests is challenging. An easy way to fix the problem is to accept most things as parameters to the method. 

Welcome to the fluent world

Being a big follower of fluent way of writing code (read more about this here) I re-factored this code. Here is how the code for controller set up looks in fluent world
var documentController = Build.Controller<DocumentController>()
                              .Having(b =>
                                {
                                    b.PostRequest().At("http://localhost/api/content");
                                    b.UsingDefaultRoute().HavingRouteData("controller", "document");
                                });
Doesn't this read natural - "Build a controller named DocumentController having a port request at "http://localhost/api/content" and using default route and route data 'controller' having a value of 'document'". It indeed reads better and is easy to repeat in multiple tests with changing request types, routes and route data. 

But what is going on here. 

Lets analyse this code line by line. On first line we call  


Build.Controller<DocumentController>()
Build is a static class having a method Controller<T>. This gives us an instance of a class called ControllerBuilder<T>. Here is the code for both of these classes
public static class Build
    {
        public static ControllerBuilder<T> Controller<T>() where T : ApiController, new()
        {
            return new ControllerBuilder<T>();
        }
    }

public class ControllerBuilder<T> where T: ApiController, new()
    {
        private RequestBuilder requestBuilder;
        private RouteBuilder routeBuilder;

        public RequestBuilder PostRequest()
        {
            requestBuilder = new RequestBuilder(HttpMethod.Post);
            return requestBuilder;
        }

        public RouteBuilder UsingRoute(string name, string value)
        {
            routeBuilder = new RouteBuilder(name, value);
            return routeBuilder;
        }

        public RouteBuilder UsingDefaultRoute()
        {
            return UsingRoute("DefaultApi", "api/{controller}/{id}");
        }

        public T Having(Action<ControllerBuilder<T>> builder)
        {
            builder(this);
            return Build();
        }

        private T Build()
        {
            var configuration = new HttpConfiguration();
            var routeData = routeBuilder.BuildFor(configuration);
            var request = requestBuilder.Build();

            var controller = new T
            {
                ControllerContext = new HttpControllerContext(configuration, routeData, request),
                Request = request
            };
            controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = configuration;

            return controller;
        }
    }
There is a lot going on here..!!
 Actually not, it is same piece of code that we saw in the beginning in private method Build of ControllerBuilder class. All other methods are there in order to provide a fluent way of configuring and setting various pieces of information required to build the controller. There are two other classes referred here, namely RequestBuilder and RouteBuilder. They too are simple classes with methods that let you configure a HttpRequestMessage and route definitions in a fluent way. Here is how those classes look
public class RequestBuilder
    {
        private readonly HttpMethod httpMethod;
        private string requestUrl;

        public RequestBuilder(HttpMethod httpMethod)
        {
            this.httpMethod = httpMethod;
        }

        public RequestBuilder At(string url)
        {
            requestUrl = url;
            return this;
        }

        public HttpRequestMessage Build()
        {
            return new HttpRequestMessage(httpMethod, requestUrl);
        }
    }

public class RouteBuilder
    {
        private readonly string routeName;
        private readonly string routeValue;
        private readonly HttpRouteValueDictionary routeDataCollection = new HttpRouteValueDictionary();

        public RouteBuilder(string routeName, string routeValue)
        {
            this.routeName = routeName;
            this.routeValue = routeValue;
        }

        public RouteBuilder HavingRouteData(string name, string value)
        {
            routeDataCollection.Add(name, value);
            return this;
        }

        public IHttpRouteData BuildFor(HttpConfiguration config)
        {
            var route = config.Routes.MapHttpRoute(routeName, routeValue);
            return new HttpRouteData(route, routeDataCollection);
        }
    }
As you can see there is nothing complex in these classes, just a few methods that accept some parameters required to configure the object they are building and a method that actually builds the object.

In closing

Fluent interfaces, in my opinion, come in handy where an operation/task consists of series of steps that when written in traditional style feel unrelated. This is mostly found in the set up phase of actual operation. Hope you find this little tip helpful.

These files are also available for download as a nuget package named "FluentTestingUtils". If you are new to nuget then navigate to pacckage page on nuget to know more

Update

I just published an update to this package as I found out a tricky situation with using these classes. You can read the details here. I hope to push more and more updates in future.

No comments:

Post a Comment