Thursday, January 19, 2012

Simple Circles–MVC Controller Unit Tests (Part 5)

This post is part of a series on building a simple to use web-based contact and customer relationship management application. The goal is to support various audiences including businesses, teams, clubs, religious organizations, etc.

Simple Circles - ControllerTestsIn the last post I left off with the PeopleController refactored to use an IPartyRepository via dependency injection. Now I’ll create unit tests for the controller so that we’ll know if something breaks as the solution evolves. Since I’m planning on several views and controllers, I added a ControllerTests folder to the UnitTests project to organize and group them together. With xUnit, as with many unit testing frameworks, simply add a new class called PeopleControllerTest and begin writing the tests.

Unit testing MVC controllers generally falls into two categories:
  1. Ensuring that an action result being returned from a controller’s action is correct. On HTTP GET requests, this means the correct view result is returned.
  2. Ensuring that HTTP POSTs are behaving properly:
    1. A POST with purposely invalid model data causes it to route the request back to the originating view.
    2. A POST with correct model data causes it to route back to the Index view.
Because of the dependency injection work just done, setting up the unit test is straightforward:
[Fact]
public void DefaultGetReturnsIndexView()
{
// arrange
const string ExpectedViewName = "Index";
var peopleController =
new PeopleController(new FakeRepository.FakePartyRepository());

// act
var viewResult = peopleController.Index();

// assert
Assert.NotNull(viewResult);
Assert.Equal(ExpectedViewName, viewResult.ViewName);
var model = viewResult.ViewData.Model as IEnumerable;
Assert.NotNull(model);
}

Notice on Line #6 above that it’s easy to pass a new instance of the FakeRepository from the test method. The assertions section checks that a view result was returned, that it was the expected named view result and that the view’s model is present.


Testing the HTTP POST actions is a bit more involved. The basic test is the same however since the testing class is calling the controller directly a little more work is needed. Model validation is a part of the MVC runtime which is not present during testing so it is necessary to “prime the pump” so to speak by placing the error into the controller’s ModelState to see if it’s being handled correctly – this is essentially the same behavior the MVC runtime exhibits:

[Fact]
public void CreatePostReturnsViewIfModelStateIsInvalid()
{
// arrange
const string ExpectedViewName = "Create";
var peopleController
= new PeopleController(new FakeRepository.FakePartyRepository());
peopleController.ModelState.AddModelError("LastName", "LastName is required.");
var person = new Person
{
FirstName = "Henry",
MiddleName = "Lewis",
//LastName = "Stimson ",
Gender = Enum.GetName(typeof(GenderEnum), GenderEnum.Male),
Birthday = new DateTime(1867, 9, 21)
};

// act
var viewResult = peopleController.Create(person) as ViewResult;

// assert
Assert.NotNull(viewResult);
Assert.Equal(ExpectedViewName, viewResult.ViewName);
}

Line #8 above is where the error is set up. Lines #9 through #16 create a Person instance to pass to the controller method to mimic what the MVC runtime will do. Notice that I commented out the line for setting the LastName property for authenticity purposes. Personally, I would hate to come across some else’s code that sets up an error yet passes in perfectly valid data. Finally, the assert statements check that the same Create view is returned indicating that the error(s) were detected and handled properly.

Testing for a valid model is nearly the same – just don’t set the ModelState, pass a valid Person instance and check that we’re returned to the Index page. Edit, update and delete actions are nearly identical as well.


The final bit of code worthy of mention is around the use of AutoMapper – remember that the last post introduced AutoMapper as an implementation of the DataMapper pattern. I wired up the mapping initialization inside Global.asax.cs then. Of course the test harness doesn’t have a website since it’s job is exercising the logic and behavior of the controller classes. Therefore I have to use the xUnit way of introducing a “fixture” necessary for the test class to operate.

public class PeopleControllerTest : IUseFixture<DataMapperFixture>
{
public void SetFixture(DataMapperFixture data)
{
data.CreateMaps();
}
...
}
...
public class DataMapperFixture : IDisposable
{
public void CreateMaps()
{
Mapper.AssertConfigurationIsValid();
PersonMap.CreateMaps();
}
}

The test class implements the xUnit IUseFixture<T> interface which tells the xUnit runtime that it needs to call SetFixture with an instance of that type – in my case I created a trivial DataMapperFixture class that simply verifies the mapping is valid and calls the PersonMap.CreateMaps() method to initialize AutoMapper.


The source code for this article can be downloaded from here. The next installment will explore some simple jQuery enhancements to make the UI a little nicer and more informative.

No comments:

Post a Comment