Saturday, March 06, 2010

TDD kata for ASP.NET MVC controllers (part 1)

The purpose of this kata is to work with ASP.NET MVC controllers in isolation: a simple class library that references System.Web.Mvc and creates classes that inherit from the Controller base class, and nothing more than that.

By doing this, the goal is to understand the features of the controller in isolation, without working with the rest of the framework. The main variations in the tests with be:
* which ActionResult type is returned
* use of the Model
* use of dependency injection for mock repositories

This blog post takes you through part 1; to continue to part 2, click here.

1) Create a solution with 4 projects:
   * Tests.Unit
   * Controllers
   * Domain
   * Repository
2) Reference System.Web.Mvc in the test and controllers projects
Test #1 (ViewResult with ViewData)
3) Create WelcomeControllerTests.cs class.
4) Create test for WelcomeController. (WelcomeController must inherit from System.Mvc.Controller.)
5) Call WelcomeController.Index() and cast it to ViewResult.
6) Assert that viewResult.ViewData["Message"] equals "Welcome".
Test #2 (ViewResult with ViewData.Model)
7) Create test for WelcomeController.DisplayCustomers(List<Customer> customers) and cast it to (ViewResult)
8) Assert that viewResult.ViewData.Model is same object as customers (List<Customer>).
Test #3 (ViewResult with ViewData.Model using DI mock repository)
9) Create CustomerControllerTests.cs class.
10) Create CustomerController with constructor that requires ICustomerRepository
11) Use mockCustomerRepository to verify that FindAllCustomers() returns customers as List<Customer>   
12) Call CustomerController.DisplayCustomers() and cast to (ViewResult)
13) Assert that viewResult.ViewData.Model and customers are the same (equivalent) object.
Test #4 (RedirectToRouteResult)
14) Create HelpTopicsControllerTests.cs class.
15) Add a reference to System.Web.Routing, in 2 projects: Tests and Controllers.
16) Create test which validates that HelpTopicsController.GetHelp(string helpTopic) does the following:
    a) returns a RedirectToViewResult.
    b) Asserts that redirectToRouteResult.RouteValues for keys "controller", "action", and "helpTopic" return expected results. (see code sample below for specific values.)

WelcomeControllerTests.cs

[TestFixture]
public class WelcomeControllerTests
{
    [Test]
    public void IndexMethod_NoParameters_ReturnsViewResultMessageWelcome()
    {
        var welcomeController = new WelcomeController();
        var viewResult = (ViewResult)welcomeController.Index();

        Assert.AreEqual("Welcome", viewResult.ViewData["Message"]);
    }

    [Test]
    public void DisplayCustomersMethod_CustomersListParameter_ReturnsCustomersAsModel()
    {
        var welcomeController = new WelcomeController();
        var customers = new List<Customer>();
        var viewResult = (ViewResult)welcomeController.DisplayCustomers(customers);

        Assert.AreSame(customers, viewResult.ViewData.Model);
    }
}


WelcomeController.cs

public class WelcomeController : Controller
{
    public ViewResult Index()
    {
        ViewData.Add("Message", "Welcome");
        return View();
    }

    public ViewResult DisplayCustomers(List<Customer> customers)
    {
        return View(customers);
    }
}


CustomerControllerTests.cs

[TestFixture]
public class CustomerControllerTests
{
    private MockRepository _mockRepository;
    private ICustomerRepository _mockCustomerRepository;

    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository();
        _mockCustomerRepository = _mockRepository.StrictMock();
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.ReplayAll();
        _mockRepository.VerifyAll();
    }

    [Test]
    public void FindAllCustomersMethod_NoParams_ReturnsCustomersAsModel()
    {
        var customerController = new CustomerController(_mockCustomerRepository);
        var customers = new List<Customer>();
        Expect.Call(_mockCustomerRepository.FindAll()).Return(customers);

        _mockRepository.ReplayAll();

        var viewResult = (ViewResult) customerController.FindAllCustomers();
        Assert.AreSame(customers, viewResult.ViewData.Model);
    }
}


CustomerController.cs

public class CustomerController : Controller
{
    private readonly ICustomerRepository _customerRepository;

    public CustomerController(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public ViewResult FindAllCustomers()
    {
        var customers = _customerRepository.FindAll();
        return View(customers);
    }
}


HelpTopicsControllerTests.cs

[TestFixture]
public class HelpTopicsControllerTests
{
    [Test]
    public void FindTopicMethod_StringInput_ReturnsRedirectToRouteResult()
    {
        const string helpTopic = "searching";
        var helpTopicsController = new HelpTopicsController();
        var redirectToRouteResult = helpTopicsController.GetHelp(helpTopic);

        Assert.AreEqual("helpTopic", redirectToRouteResult.RouteValues["controller"]);
        Assert.AreEqual("index", redirectToRouteResult.RouteValues["action"]);
        Assert.AreEqual(helpTopic, redirectToRouteResult.RouteValues["helpTopic"]);
    }
}


HelpTopicsController.cs

public class HelpTopicsController : Controller
{
    public RedirectToRouteResult GetHelp(string helpTopic)
    {
        return RedirectToAction("index", "helpTopic", new {helpTopic = helpTopic});
    }
}

No comments: