In an earlier post/03/test-automation-pyramid-asp-net-mvc/ I said that I unit test my controllers. What I didn’t say was that I (with most of the work done by my colleague Mark and field tested by Gurpreet) had write code to make this possible. To unit test our controllers we put a layer of code that was an abstraction for “redirections”. This has turned out to be very successful for the line of business applications we write. Mark coined this a fluent controller. Out fluent controller has these benefits:
- test-first design of controller redirections
- also isolate that the controller makes the correct repository/service calls
- lower the barrier to entry by developer new to MVC
- avoids a fat controller antipattern
- standardises flow within actions
I also tend toward a REST design in the application so we also wanted it to live on top of the SimlyRestful contrib. You’ll find there’s an abstract class both with and without simplyRestful. I will make a quick, unsubstantiated comment. The fluent controller does not actually help you create a controller for a REST application – it is Restful but really not a full implementation of REST.
Designing Controller Actions
In a simplyRestful design, the flow is standardised per resource. We have out 7 actions that we decide what is going to be implemented.

How to write it test first?
In words, I have a User Controller that displays a user and I can do the usual CRUD functions. These examples are taken from the MvcContrib source unit tests
This test, I enter in on Index and it should just render itself and I don’t need to pass anything in:
using MvcContrib.TestHelper.FluentController;
[Test]
public void SuccessfulIndex()
{
GivenController.As<UserController>()
.ShouldRenderItself(RestfulAction.Index)
.WhenCalling(x => x.Index());
}
Got the hang of that one? Here’s some ones that redirect based on whether or not the repository/service call was successful or not. This example, imagine, you have just said Create me the user:
[Test]
public void SuccessfulCreateRedirectsToIndex_UsingRestfulAction()
{
GivenController.As<UserController>()
.ShouldRedirectTo(RestfulAction.Index)
.IfCallSucceeds()
.WhenCalling(x => x.Create(null));
}
[Test]
public void UnsuccessfulCreateDisplaysNew_UsingString()
{
GivenController.As<UserController>()
.ShouldRenderView("New")
.IfCallFails()
.WhenCalling(x => x.Create(null));
}
Here’s a test that ensures that the correct status code is returned. In this case, I will have done a GET /user and I would expect a 200 (OK) result. This is very useful if you want to play nicely with the browser – MVC doesn’t also return the status code you expect.
[Test]
public void ShowReturns200()
{
GivenController.As<UserController>()
.ShouldReturnHead(HttpStatusCode.OK)
.WhenCalling(x => x.Show());
}
The .ShouldReturnHead was a helper method, it actual delegated by to .Should which you too can use to write your own custom test helpers:
[Test]
public void GenericShould()
{
GivenController.As<UserController>()
.Should(x => x.AssertResultIs<HeadResult>().StatusCode.ShouldBe(HttpStatusCode.OK))
.WhenCalling(x => x.Show());
}
Now we can start combining some tests. In this case, we want to create a new customer and if it does, render the New view and ensure that ViewData.Model has something in it (and we could check that it is customer).
[Test]
public void ModelIsPassedIntoIfSuccess()
{
var customer = new Customer { FirstName = "Bob" };
GivenController.As<UserController>()
.ShouldRenderView(RestfulAction.New)
.Should(x => x.AssertResultIs<ViewResult>().ViewData.Model.ShouldNotBeNull())
.IfCallSucceeds(customer)
.WhenCalling(x => x.Create(customer));
}
Sometimes, we only want return actions based on header location, so we can set this up first .WithLocation.
[Test]
public void HeaderSetForLocation()
{
GivenController.As<UserController>()
.WithLocation("http://localhost")
.ShouldReturnEmpty()
.WhenCalling(x => x.NullAction());
}
There is also access to the Request through Rhino Mocks, try it like this.
[Test]
public void GenericHeaderSet()
{
GivenController.As<UserController>()
.WithRequest(x => x.Stub(location => location.Url).Return(new Uri("http://localhost")))
.ShouldReturnEmpty()
.WhenCalling(x => x.CheckHeaderLocation());
}
Post a Comment