In normal Asp.net web applications, writing precise unit test cases with high code coverage is difficult because of the strong coupling of the GUI and the server code. But Asp.Net MVC is a framework that Microsoft has built in a completely unit testable way. 100% unit test case code coverage can be achieved in Asp.Net MVC applications because the Models, Views and Controllers are very loosely coupled.
In this article I will be explaining about the support for test driven development and writing unit test cases in an Asp.Net MVC application.
Test Driven Development (TDD) and Best Practices
Test Driven Development is the process where the developer creates the test case first and then fixes the actual implementation of the method. It happens this way, first create a test case, fail it, do the implementation, ensure the test case success, re-factor the code and then continue with the cycle again as indicated in Fig 1.0.
Fig 1.0 – Test Driven Development
TDD also states that every code which is deployed to production should be covered by the unit test case.
The best practices in writing the unit test cases are to follow the F.I.R.S.T principles. Here is a brief explanation for each of them.
Fast – Write the unit test cases by keeping their performance in mind. This is required because you will have to run thousands of them during every release.
Isolated – Each test case to be isolated to a particular behavior i.e. in-case of a failure the developer should know what went wrong without having to go through the execution flow. A test case should be broken down into multiple smaller ones for this purpose.
Repeatable – The test case should be stable so that it provides the consistent results over multiple runs.
Self validating – The test should result in a pass or a failure. There should not be any ambiguity scenarios with assertions.
Timely – This is more important for TDD as the test cases should be created before the actual implementation is.
TDD with Asp.Net MVC
Asp.Net MVC is a perfect adoption for unit testing since the entire server code is embedded in the controllers, which are de- coupled from other layers like GUI (View). When you create an Asp.Net MVC application Visual Studio by default will prompt for adding a Unit Test case project as shown in Fig 2.0. This should show how well the MVC architecture would support writing Unit Tests.
Fig 2.0 – Prompt for adding a Unit Test case project
Testing the Controller
Let us take a look at few samples that will demonstrate writing the unit test cases for the Controllers in an As.Net MVC application. Create an Asp.Net MVC application in Visual Studio 2012 and check the option of creating a unit test project by default. Let’s assume that we are creating an online movie store application. In the MVC application create a controller named MovieStoreController, a model named Movie; for now we don’t create any views, which can be done at a later point in time.
The requirement is to display a welcome message to the user on the MovieStore index and to list the movies from the application database.
As it is a TDD we should write the test case first for the requirement and then proceed with the implementation. In the UnitTest project add a unit test class named MovieStoreControllerTest. As a standard the naming convention followed in MVC for the unit test classes is the controller name followed by ‘Test’. First let’s concentrate on building the functionality for the welcome message requirement. The following code is the test case to validate the welcome message.
namespace TddWithMvc.Tests { [TestClass] public class MovieStoreControllerTest { [TestMethod] public void MovieStoreProvidesCorrectWelcomeMessage() { MovieStoreController movieController = new MovieStoreController(); ViewResult result = movieController.Index() as ViewResult; Assert.AreEqual("Welcome to the movie store!", result.ViewBag.Message); } } }
The index returns the ViewResult, as the MVC controller methods will return Views, unlike normal Asp.net C# methods. Running the above test case will fail and let us implement the Index method in the controller as shown below, allowing the functionality to pass. This is how the items in the controller ViewBag can be unit tested.
namespace TddWithMvc.Controllers { public class MovieStoreController : Controller { public ActionResult Index() { ViewBag.Message = "Welcome to the movie store!"; return View(); } } }
Let us now write the unit test case for the Movie list functionality.
[TestMethod] public void MovieStoreReturnsTheListOfMovies() { MovieStoreController movieController = new MovieStoreController(); ViewResult result = movieController.Movies() as ViewResult; var movies = result.Model as List<Movie>; Assert.AreEqual(movies.Count, 200); } public ActionResult Movies() { var movies = moviesDataAccess.GetMoviesFromDatabase(); return View(movies); }
Now go and do coding until the above test case goes for a pass. Below is the implementation, which fetches the movies from the database and encapsulate it with the View.
public ActionResult Movies() { var movies = moviesDataAccess.GetMoviesFromDatabase(); return View(movies); }
Running the unit test case will succeed as the current movie count is 200 in the database. But what if a few more movies are added to the database? Or what if the database connection in the test case execution environment does not go through? The test case will start failing. The intention of the unit test case is now broken. The intention was to test only the controller method if it returns the list of movies and not to test the number of records in the database or the database connectivity. This is when you need to still decouple the dependent classes. One of the ways to achieve it is by doing dependency injection.
To correct the above unit test case, let us introduce dependency injection. Create an interface to mock data access object and inject it while creating the controller instance. Here is the code for interface and the test case using the mock object.
public interface IDataAccessManager { List<Movie> GetMoviesFromDatabase(); }
In the test method notice that the mock object is being injected into the controller constructor.
[TestMethod] public void MovieStoreReturnsTheListOfMovies() { MovieStoreController movieController = new MovieStoreController(new MockDataAccessManager()); ViewResult result = movieController.Movies() as ViewResult; var movies = result.Model as List<Movie>; Assert.AreEqual(movies.Count, 2); }
Now in the implementation we will have to make the changes to use the actual data access class during the application execution and to use the mock object only during unit test case. Following is the code to do it exactly as required. Notice that the default constructor is explicitly injecting the concrete data access object.
IDataAccessManager moviesDataAccess; public MovieStoreController(IDataAccessManager dataAccessManager) { moviesDataAccess = dataAccessManager; } public MovieStoreController():this(new MoviesDataAccessManager()) { } public ActionResult Movies() { var movies = moviesDataAccess.GetMoviesFromDatabase(); return View(movies); }
For testing the Views you may have to use automation testing software like Selenium, Coded UI, etc.
Happy reading!