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

  1. The ViewResult should not be null.
  2. The ViewResult should be an instance of PagedList<Category> that supports paging.
  3. The category count should be 3.
  4. 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.

  1. The category name should be required. If name is empty, an error message should be set into model state.

  2. 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.

3 Comments

  • Thanks for a well-written explanation, Shiju.

    I have one question: When doing TDD of an MVC site, do you do your design from the top-down (e.g., starting with something like "A customer should be able to enter an order", with all that that requirement encompasses), or from the bottom-up (e.g. starting with a low-level requirement like "A valid SKU should return exactly one product from the product repository")?

    Thanks.

  • Nice simple overview.. really easy to follow for us rookies... Got the model and controller. Now I just need to figure out the "View"

  • Whats the point of the Service class? What is it's single responsibility? The first test highlights the issue. You're testing a controller that consumes the service class, yet all your mocks and expectations are set on the repository class.

    Indeed your SetUp method actually creates an instance of the service which means that your controller tests will test both the behavior of the service and the controller. Any change to actual behavior in the service class would likely result in broken controller tests.

    In looking at the behavior encapsulated by the code I see only a single method that shouldn't be on the repository or controller and that is the validation of the name. Would saving a category to the DB with an empty name ever be a valid condition? If not then the validation of the category should be moved to a place where this logic can be shared.

    Because the logic is so simple here, I would simply place the validation logic on the model directly, but for many more advanced scenarios the logic should be encapsulated on a different object. The more advanced scenarios would then drive the creation of a Service object of some sort.

    Overall good explanation of TDD, but the service object is simply unnecessary for the example.

Comments have been disabled for this content.