This is Part 4 in a series on unit testing MVC. Part 1 and an index to all posts in this series is here.
In my last post in this series, I introduced you to mocks and explained there are still issues going on, even though the tests pass and the code runs correctly. The issue I mentioned is that we need to remove the need for multiple constructors and we can see why by looking at the Create method.
1: public ActionResult Create()
3: ViewBag.StateCode = new SelectList(db.States.OrderBy(s => s.Name), "Code", "Name");
4: ViewBag.SalutationId = new SelectList(db.Salutations.OrderBy(s => s.Name), "Id", "Name");
5: return View();
Here we create two SelectLists to handle the DropDown boxes on the create form. Do you see a problem? The code calls into the database instead of using repositories. So, in order to unit test, we need to pass in mocked repositories. We can do this in a few different ways.
First, we can add two additional parameters to the second constructor so that we pass not only mocked repositories for the Customer, but also the State and Salutation. That means we have to mock those even if we don’t use them in the test. This is bad practice. The test should only create what it needs.
Second, we can add another constructor that passes all three mocked repositories. But this means as we add functionality, we may need to add more and more constructors just to test the code. I have production code with forms that have a dozen or more dropdowns. This gets very complicated and difficult to maintain. Again, a bad practice.
The real solution is to use dependency injection. Here’s the Wikipedia definition
Dependency injection is a software design pattern that allows removing hard-coded dependencies and making it possible to change them, whether at run-time or compile-time.
This can be used, for example, as a simple way to load plugins dynamically or to choose stubs or mock objects in test environments vs. real objects in production environments. This software design pattern injects the depended-on element (object or value etc) to the destination automatically by knowing the requirement of the destination.
So, what we’re going to do is add a dependency injection (DI) controller that will say, “If ICustomerRepository object is not passed into the class, then use the default. If one is passed in, use it.”
Once again, the first step is to use NuGet to get the DI controller installed into the solution. There are several DI controllers you can choose from. We’re going to use one called Ninject. The first step is to right-click on the References node of the MVCUnitTests project (this is our main project, not the one that has the actual unit tests) and select Manage NuGet Packages. Search online for Ninject and when found, click Install.
Notice in this dialog that Ninject is described as an “IoC container”. IoC stands for Inversion of Control, basically another name for Dependency Injection. Now we need to do just a little coding to make this work. To start, add a new class named NinjectControllerFactory.cs to the App_Start folder of the project.
1: using System;
2: using System.Web.Mvc;
3: using System.Web.Routing;
4: using MVCUnitTesting.Models;
5: using Ninject;
7: namespace MVCUnitTesting.App_Start
9: public class NinjectControllerFactory : DefaultControllerFactory
11: private IKernel ninjectKernel;
13: public NinjectControllerFactory()
15: ninjectKernel = new StandardKernel();
19: protected override IController
20: GetControllerInstance(RequestContext requestContext, Type controllerType)
22: return controllerType == null ? null :
26: private void AddBindings()
1: ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
Finally, we need to go back to the CustomerController and remove the default constructor. Remember, Ninject will handle injecting an EFCustomerRepository if we don’t pass in one that is mocked in the tests.
Your tests should still be green and if you run the application and navigate to the Customer Index, data should be pulled from the database. But we’re still not done. Remember the references to setup the SelectLists I mentioned at the beginning of this post? We need to add repositories for those tables and then tests for the remaining methods of the CustomerController. I’ll leave the States and Salutations tables for you. In the next post, I’ll begin to address the remaining CustomerController methods.