Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

Improving your MVC Unit Tests with Interfaces, Dependency Injection, and Mocking

Rate me:
Please Sign up or sign in to vote.
4.94/5 (11 votes)
2 Nov 2014CPOL9 min read 56.6K   27   5
An introduction to developing and testing MVC controllers that are loosely coupled to heavy weight service classes that use external services such as SQL databases.

Introduction

One of the main benefits of ASP.NET MVC is that you can separate your HTML/Javascript/Razor views from the server-side C# logic in your controllers. There controllers can be tested separately from the views by using unit tests so that the code inside can be validated.

However a different set of problems comes into play when your MVC controllers use classes that interact with external services such as databases. Uness there is a database online and available for unit tests, the code within the controllers cannot be validated within a testing framework like NUnit or the Visual Studio Testing Framework. Your controllers might also use other services such as SMTP or SQL Reporting Services that are even more difficult to abstract in your unit tests. Setting up these external services just for your unit tests can be very cumbersome and time consuming.

In some cases you may want to run end-to-end automated tests (a.k.a. integration tests) that include testing how your code interacts with these external services. However if you are workin in continuous integration environment, where all unit tests are executed upon every code commit, it is not feasible to run these types of integrations tests after every commit because of the CPU and time resource consumption.

This article explains how you can loosely couple your ASP.NET MVC controllers with service classes that use these external services using interfaces and dependency injection and seting up a mocking framework for your unit tests so that these external services are not required to run controller tests.

The diagram below illustrates what we will accomplish in this article. We will create our MVC controllers such that under different runtime environments, the same code will have different concrete implementations of the same interface. We will use Ninject for the dependency injector in our ASP.NET MVC runtime environment and we will use Moq to create a functional proxy class in our unit testing environment.

 

Image 1

Background

This article will use sample code to illustrate the techniques. The solution that contains the sample code can be downloaded at the following github location:

https://github.com/pcarrasco23/FootlooseFinancialServices

To build and run the unit tests in the solution you will need at a minimum the express version of Visual Studio 2013.

For Dependency Injection we will be using Ninject. http://www.ninject.org/

For the mocking framework we will be using Moq. https://github.com/Moq

Tightly-coupled Controller

The busiest controller in this solution is Controllers\PersonController.cs in the FootlooseFS.Web.AdminUI project. This controller provides the code-behind for the view to perform CRUD operations on the Person entity in a SQL database. At one point during the initial prototype the initialization code of PerconTroller appeared like below:

C#
public class PersonController : Controller
{ 
   private readonly FootlooseFSService _footloosFSService;

   public PersonController()
   {
       _footlooseFSService = new FootloosFSService();
   }

   ...

   [AcceptVerbs(HttpVerbs.Post)]
   [Authorize(Roles = "Admin,Demographics")]
   public JsonResult Save(FormCollection formCollection)
   {
        ...
        _footlooseFSService.UpdatePerson(person);
        ...
   }

   ...

The object FootlooseFSService is the service class that provides access to the external SQL database. At this point the PersonController is tightly-coupled to the FootlooseFSService class because it does not use an interface. This makes it very difficult to test the Save method without invoking the SQL database. There are some indirect ways around this problem (such as wrapping the service call in a protected method like insertPerson() and creating a derived Controller class in the unit test that overrides this method). But the more direct approach would be to create an interface for the service class and refactor the PersonController to use the interface instead.

Service Interface

I created an interface for the FootlooseFSService class called IFootlooseFSService (If you do this enough times you will learn to always create the interface before the class) in the FootlooseFS.Service project. Then I modified PersonController to use the service interface instead of the service class.

C#
public class PersonController : Controller
{ 
   private readonly IFootlooseFSService _footlooseFSService;

   public PersonController(IFootlooseFSService footlooseFSService)
   {
       _footlooseFSService = footlooseFSService;
   }

   ...

   [AcceptVerbs(HttpVerbs.Post)]
   [Authorize(Roles = "Admin,Demographics")]
   public JsonResult Save(FormCollection formCollection)
   {
        ...
        _footlooseFSService.InsertPerson(person);
        ...
   }

   ...

Note that the new version of PersonController no longer has a parameter-less constructor. That is because we cannot "new" an interface. We have to pass in an object that implements the interface which will be referenced within the controller by the interface signature.

The code in PersonController will be executed within two different environments: from within runtime of the ASP.NET MVC engine and from within the Visual Studio Unit testing framework. In both of these runtime environments the IFootlooseFSService interface will need to be automatically replaced with an implementation of the interface within PersonController. This process is known as dependency injection.

ASP.NET MVC Runtime Dependency Injection with Ninject

Comparing the tighly-coupled PersonController with the loosely-coupled one, you will note that PersonController no longer has a parameter-less constructor. In order for ASP.NET MVC to use the PersonController class, it needs to be told how to resolve the IFootlooseFSService dependency. 

Ninject is an open-source dependency injecter that when loaded within a .NET runtime replace an interface with an object of a class that implements the interface. Using Ninject requires the follow basic steps: 

1. Install Ninject and reference it in your ASP.NET MVC project using NuGet.

2. Implement the IDependencyResolver interface that tells ASP.NET MVC how to resolve dependencies at runtime.

3. Provide Ninject with the mappings from interface to class in the ASP.NET MVC startup code.

If you are using the FootlooseFS.Web.AdminUI project then you can skip the first step since I have Ninject in the packages folder of my solution with a reference to it in the FootlooseFS.Web.AdminUI ASP.NET MVC project. Otherwise if you are using your own project you can download and install Ninject from NuGet.

Next we have to tell ASP.NET MVC how to resolve the interface dependencies using Ninject when it loads the controller. We can do this by creating a class that implements the ASP.NET MVC IDependencyResolver interface. This class uses Ninject's IResolutionRoot object to access the mappings between the interfaces and the concrete classes.

The code below is in the file NinjectDependencyResolver.cs. If you are creating your own project add this file to the ASP.NET MVC project

C#
public class NinjectDependencyResolver : IDependencyResolver
{
    private readonly IResolutionRoot _resolutionRoot;

    public NinjectDependencyResolver(IResolutionRoot kernel)
    {
        _resolutionRoot = kernel;
    }

    public object GetService(Type serviceType)
    {
        IRequest request = _resolutionRoot.CreateRequest(serviceType, null, new Parameter[0], true, true);
        return _resolutionRoot.Resolve(request).SingleOrDefault();
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        IRequest request = _resolutionRoot.CreateRequest(serviceType, null, new Parameter[0], true, true);
        return _resolutionRoot.Resolve(request);
    }
}

From where does the IResolutionRoot object get its interface to class mapping? We will add code to the ASP.NET MVC startup that tells Ninject how we want to map the interface to the class. In this example we are telling Ninject that we want it to instantiate a new instance of the FootlooseFSService class whenever it receives a request to resolve the IFootlooseFSService interface. Also we are telling the ASP.NET MVC runtime that we want to it use our NinjectDependencyResolver that we created when it needs to resolve dependencies in the MVC controllers.

The code below is in the file Startup.cs. If you are creating your own project add this file to your ASP.NET MVC project.

C#
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var kernel = new StandardKernel();

            kernel.Bind<IFootlooseFSService>().To<FootlooseFSService>();
            kernel.Bind<IFootlooseFSUnitOfWorkFactory>().To<FootlooseFSSqlUnitOfWorkFactory>();

            DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
        }
    }

Note that in order for Ninject to be able to create a new instance of the FootlooseFSService class, this class either must have a paramter-less constructor or the parameters of it's constructor also must be injected at this point. Because FootlooseFSService has a parameters in it's constructor of the type IFootlooseFSUnitOfWorkFactory, we must also create a mapping between a class that implements this interface and the interface as well.

Now when we run our ASP.NET MVC web site in realtime, Ninject will be running behnd the scenes within the IIS worker process returning new instances of classes when requested.

Unit Testing with Mock objects using Moq

Now it is time to create our unit test (If you are a TDD purist you will say that we should have created the unit test first before the service but I will let you choose which one to create first once you know the techniques). The PersonController has a method called Save and we want to create one or more unit tests that validate the correctness of the code in the Save method. Because this method uses the IFootlooseFSService interface we need a way to pass in an object instance that satisfies the interface when the unit tests runs. We do not necessarily need to test the code within the instance of IFootlooseFSService. We only need it to function correctly so that we can test the rest of the code inside of the Save method.

The Moq mocking framework for .NET gives us the ability to create a proxy object of a particular interface that we can use within our controller. This proxy will be used in place of a real instance of the interface within the MVC controller.  To create and use the proxy class first I initialize a mock of the IFootlooseFSService interface.

C#
var mockFootlooseFSService = new Mock<IFootlooseFSService>();

Then I stub the UpdatePerson method of the interface using the Setup method of the mock. We can make the stubbed method return any value that we want as long as it is of the output type of the method. In the example below I am instructing the mock that when the UpdatePerson method is used, return an object of type OperationStatus which contains the original Person object that was passed into it.

C#
mockFootlooseFSService.Setup(m => m.UpdatePerson(It.IsAny())).Returns((Person p) => { return SetupOperationStatus(p); });

In the test we create an instance of PersonController and pass in an instance of the mocked interface by using the Object property. Here we are basically injecting the dependency by hand instead of relying on automatic dependency injector like Ninject.

C#
PersonController personController = new PersonController(mockFootlooseFSService.Object);

When the unit test is running the PersonController object has a functioning instance of the service class and we do not need to worry about setting up a database or any other external service for the test. Here is the code for the unit test. If you are using my project it will be the file PersonControllerTests.cs in the project FootlooseFS.Web.AdminUI.Tests.

C#
[TestClass]
public class PersonControllerTests
{
    private Mock<IFootlooseFSService> mockFootlooseFSService;

    [TestInitialize]
    public void SetupTests()
    {
        mockFootlooseFSService = new Mock<IFootlooseFSService>();

        mockFootlooseFSService.Setup(m => m.UpdatePerson(It.IsAny())).Returns((Person p) => { return SetupOperationStatus(p); });
    }

    private OperationStatus SetupOperationStatus(Person p)
    {
        var opStatus = new OperationStatus();
        opStatus.Data = p;

        return opStatus;
    }

    [TestMethod]
    public void TestPersonSave()
    {
         PersonController personController = new PersonController(mockFootlooseFSService.Object);

        FormCollection formCollection = new FormCollection();
        formCollection.Add("personID", "1");

        ...

        ActionResult result = personController.Save(formCollection);

        ...
    }
}

When the Save method runs within the unit test, it will be using the stubbed version of the UpdatePerson method of the interface.

Conclusion

In this article you saw the value of loosely coupling our controllers to service classes using interfaces, dependency injection, and mocking in order to unit test controllers that rely on external services.

If you are researching alternative dependency injectors for .NET you should consider wither Castle Windsor or Unity. For mocking frameworks, in addition to Moq you can also consider using Rhino Mocks.

This article is very rudimentary and I encourage you to learn more about dependency injection. There are many great articles out there on the topic including this one from the man who coined the phrase Martin Fowler:

http://martinfowler.com/articles/injection.html

I hope that you found this article useful and please feel free to comment on any additional insights.

Thanks!

License

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


Written By
Team Leader
United States United States
I am a senior software engineer and technical lead for a mid-sized technology firm. I have an extensive background in designing, developing, and supporting ASP.NET web-based solutions. I am currently extending my skill-set to the area of application identity management and access control.

Comments and Discussions

 
QuestionIssue in 2.1 EFCore project with serviceClass unit testing. Pin
amit_8315-Nov-18 2:55
professionalamit_8315-Nov-18 2:55 
GeneralComment Pin
Rams ch12-Aug-15 17:58
Rams ch12-Aug-15 17:58 
QuestionThanks for posting this! Pin
Your Display Name Here15-Mar-15 15:57
Your Display Name Here15-Mar-15 15:57 
AnswerRe: Thanks for posting this! Pin
Peter Carrasco17-Mar-15 16:18
Peter Carrasco17-Mar-15 16:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.