Click here to Skip to main content
15,885,278 members
Articles / DevOps / Unit Testing
Tip/Trick

Unit Testing MVC Web API

Rate me:
Please Sign up or sign in to vote.
4.33/5 (6 votes)
17 Apr 2016CPOL1 min read 12K   6  
Make MVC Web API code unit testable from controller

Introduction

To make a Web API unit testable, we have to initialize the dependent interfaces via the controller’s constructor. The challenge with MVC Web API is that when an HttpRequest is made, the IHttpControllerActivator class tries to initialize the controller based on which controller has been selected and passed on by HttpControllerDispatcher class. By default, IHttpControllerActivator looks for a parameter-less constructor. Hence, if we are using dependency injection (DI) in our Web API, we usually get errors that look like this:

C#
{  
   "Message":"An error has occurred.",
   "ExceptionMessage":"An error occurred when trying to 
   create a controller of type <your controller name>. 
   Make sure that the controller has a parameterless public constructor.",
   "ExceptionType":"System.InvalidOperationException",
   "StackTrace":"   
   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create
   (HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, 
   Type controllerType)\r\n   
   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController
   (HttpRequestMessage request)\r\n   
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
   "InnerException":{  
      "Message":"An error has occurred.",
      "ExceptionMessage":"Type <controller type> does not have a default constructor",
      "ExceptionType":"System.ArgumentException",
      "StackTrace":"   at System.Linq.Expressions.Expression.New(Type type)\r\n   
      at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n   
      at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator
      (HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n   
      at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create
      (HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, 
      Type controllerType)"
   }
}

Solution

To solve this problem and still have a unit testable code written using DI, we have to register a dependency resolver with our HttpConfiguration class.

The Code

In the WebApiConfig.cs file, where all the configurations are registered with our HttpConfiguration class, we have to assign our UnityContainer that holds all the dependency resolution rules, to the DependencyResolver property of HttpConfiguration instance.

C#
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var container = <Register all dependencies here>;
        config.DependencyResolver = new UnityResolver(container);

        // Web API configuration and services


        // Web API routes
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

    }
}

The moment this registration of dependency resolver is done, the framework knows how to resolve the interfaces that are needed in the controller’s constructor, and hence can get through the GetInstanceOrActivator method successfully.

Point of Interest

This kind of registrations and DI hooking helps us start the unit testing right from the controller methods as compared to testing only the business libraries separately. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Hi, I am Purbasha and I have been into web development, clean code promotion and benchmarking new Azure offerings since quite a while now. I am here to share my learnings and solutions/hacks that I keep collecting with my experience.

Comments and Discussions

 
-- There are no messages in this forum --