Introduction to Test-Driven Development with ASP.NET MVC
One of the greatest advantages of ASP.NET MVC is the support of testability, which enables to Test-Driven Development (TDD) in an easy manner. A testable application should be developed in a loosely coupled manner so that you can test the independent parts of the application. For developing testable applications, the support of developer frameworks is very important and the frameworks should be designed to facilitate building testable applications. One of the design goals of ASP.NET MVC was testability so that you can easily develop testable application with the ASP.NET MVC framework. In this post, I will give an introduction to Test-Driven Development (TDD) with ASP.NET MVC using NUnit unit test framework.
Introduction to Test-Driven Development
TDD is a design approach that follows a Test First development where you write a test before you write just enough production code to fulfill that test and refactoring. The TDD model development is also known as Red-Green-Refactor style development.
The following are the workflow of TDD
1. Requirement identified
2. Write an automated test
3. Run tests and make sure new one FAILS (RED)
4. Write some code
5. Run tests to make sure all PASS (GREEN)
6. Refactor
7. Repeat
In this example, I will explain a scenario where users can view list of categories and also able to add new category into the application. The following are the behaviors of our simple reference application
1. Users should be able to view list of Categories
2. The category list should support paging
3. The Category Name should be mandatory while creating a new Category
Code listings of the application
Category model
public class Category {
public int CategoryID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
ICategoryRepository
public interface ICategoryRepository {
IQueryable<Category> GetCategories();
void Save(Category category);
void Delete(int id);
Category Read(int id);
}
ICategoryService
public interface ICategoryService {
IQueryable<Category> GetCategories();
void Save(Category category);
Category Read(int id);
void Delete(int id);
}
CategoryController - List Action
public ActionResult List(int? page)
{
return View();
}
Currently the List action method does not contain any implmentation logic. We will write the implementation code after writing a failure test.
Unit Tests
In TDD, we start with writing a failure test. In this post, I am using NUnit for unit test. You can download NUnit from http://NUnit.org. If you want a NUnit test template for ASP.NET MVC, check the blog post Updated NUnit Templates for ASP.Net MVC 1.0 RTM .
The below is the test for the behaviours of the category list
[TestFixture]
class CategoryControllerTest
{
Mock<ICategoryRepository> _repository;
ICategoryService _service;
[SetUp]
public void Setup()
{
_repository = new Mock<ICategoryRepository>();
_service = new CategoryService(_repository.Object);
}
[Test]
public void List()
{
IQueryable<Category> fakeCategories = new List<Category> {
new Category { Name = "Test1", Description="Test1Desc"},
new Category { Name = "Test2", Description="Test2Desc"},
new Category { Name = "Test3", Description="Test3Desc"}
}.AsQueryable();
_repository.Setup(x => x.GetCategories()).Returns(fakeCategories);
CategoryController controller = new CategoryController(_service);
// Act
ViewResult result = controller.List(null) as ViewResult;
// Assert
Assert.IsNotNull(result, "View Result is null");
Assert.IsInstanceOf(typeof(PagedList<Category>),
result.ViewData.Model, "Wrong ViewModel");
var categories = result.ViewData.Model as PagedList<Category>;
Assert.AreEqual(3, categories.Count,"Got wrong number of Categories");
Assert.AreEqual(0, (int)categories.PageIndex,"Wrong page Index");
Assert.AreEqual(1, (int)categories.PageNumber,"Wrong page Number");
}
The Unit Test class decorated with TextFixure attribute which specify that it is unit test class. The test attribute is an indication that it is a test method. The method with Setup attribute will call before execute every test method. This method is using for initialization purpose. If you have five test methods, it will call five times. In the above test, I have used mock objects. Mock objects are simulated objects that can mimic the behaviour of real objects. This is very useful when real object is impractical to incorporate into a unit test such as you don't want to hit the database. In this case, I don't want to hit the database. Instead, I created three fake category objects and setup that returns this fake category objects when there is a call for GetCategories method of the Categoryrepository object.
_repository.Setup(x => x.GetCategories()).Returns(fakeCategories);
The Mock<ICategoryRepository> contains the methods for the behaviour of Mock repository and the repository.Object reperesent the actual repository object. For mocking, I am using the Moq framework. You can download Moq framework from http://code.google.com/p/moq.
The Assert class is using for verfying the results. The test verify the following behaviour with the three fake category objects
- The ViewResult should not be null.
- The ViewResult should be an instance of PagedList<Category> that supports paging.
- The category count should be 3.
- The page index should be 0 and page number should be 1.
When running the unit test, it will fail becuase we have not written any code for List Action method.
The below shows fail status (Red Signal) from NUnit GUI test runner.
In the next step (Green stage), we have to write the code to pass the test. The below is the List action method written for pass the test.
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult List(int? page)
{
int pageSize = Convert.ToInt32(
ConfigurationManager.AppSettings["pagesize"]);
var categories = _categoryService.GetCategories().
ToPagedList<Category>(page ?? 0, pageSize);
return View(categories);
}
When running the test again, it will pass becuase we have written necessary code to pass the test. In the last stage of TDD is refactor where you will be writing code to improve the overall architcture of the application. In this stage, you can write code confidently because your code is covered by unit tests.
In the next step, you will be writing more unit tests for new identified requirements and behaviours. The below unit test is for the following behaviours while creating a new category.
-
The category name should be required. If name is empty, an error message should be set into model state.
-
After creating a new category, it would be redirect to List action method.
[Test]
public void Category_New()
{
// Arrange
CategoryController controller = new CategoryController(_service);
var category = new Category
{ Name = "Test", Description = "TestDescription" };
// Act
var result = (RedirectToRouteResult)controller.Save(category);
// Assert
Assert.AreEqual("List", result.RouteValues["action"]);
}
[Test]
public void Category_Name_Required()
{
// Arrange
CategoryController controller = new CategoryController(_service);
// Act
Category category = new Category();
category.Name = String.Empty;
var result = (ViewResult)controller.Save(category);
// Assert
var error = result.ViewData.ModelState["Name"].Errors[0];
Assert.AreEqual("Name is required.", error.ErrorMessage);
}
The below is the implementation code for the above tests.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(Category category)
{
//Validations
EnsureValidations(category);
if(!ModelState.IsValid)
{
return View("SaveCategory");
}
_categoryService.Save(category);
return RedirectToAction("List");
}
private void EnsureValidations(Category category)
{
if (string.IsNullOrEmpty(category.Name))
{
ModelState.AddModelError("Name", "Name is required.");
}
}
The below shows success status (Green Signal) from NUnit GUI test runner.
Applying Dependency Injection
In this application, I am using Dependency Injection pattern that making the application more loosely coupled and it improves the testability of the application. If you are not familiar with Dependency Injection and Inversion of Control (IoC), I recommend to read Martin Fowler’s article Inversion of Control Containers and the Dependency Injection pattern. Please check my blog post ASP.NET MVC Tip: Dependency Injection with Unity Application Block that demonstrated how you can use the Unity Application Block to perform Dependency Injection within an ASP.NET MVC application.
public static void ConfigureUnityContainer() {
IUnityContainer container = new UnityContainer();
// Registrations
container.RegisterType<DBData , DBData>
(new HttpContextLifetimeManager<DBData>());
container.RegisterType<ICategoryRepository, CategoryRepository>
(new HttpContextLifetimeManager<ICategoryRepository>());
container.RegisterType<ICategoryService, CategoryService>
(new HttpContextLifetimeManager<ICategoryService>());
ControllerBuilder.Current.SetControllerFactory(
new UnityControllerFactory(container));
}
The above code setup the dependencies with Unity Application block and configure the controller factory with Unity.
Summary
In this post, I demonstrated how to build Test-Driven Development (TDD) applications with Microsoft ASP.NET MVC. ASP.NET MVC is an excellent framework for building TDD based applications with highly loosely coupled manner.
You can download the source code from here.