Archive

Posts Tagged ‘mvc’

Fluent Controller MvcContrib – Part I – Designing Controller Actions and Redirects

March 7th, 2010 No comments

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.

Fluent Controller Action Redirects/Renders

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());
}