This is part 1 in a series of posts on using Behaviour Driven Development to build and test your MVC controllers. The full series is as follows:
Behaviour Driven Development/Design has been gaining a lot of traction recently amongst the community and whilst not everyone may be using it in anger to write code and deliver projects, most people should have a fair idea of what it is all about by now. I’ve been talking about it for a while, since I attended JP Boodhoo’s Nothin But .Net course over a year ago, where I was exposed to BDD for the first time. For the last 6 months or so, I’ve been Dev Lead on a greenfield e-commerce application, using Asp.Net MVC, Sharp Archictecture, N2 CMS, NHibernate, Spark View Engine, Post Sharp and a whole host of other cool stuff. However the main thing for me has been that it has been the first project where I’ve been able to use BDD properly, the result of which is that we’ve written more tests that any other project I’ve been on and because of that the quality of the code we’re producing is getting higher and higher each sprint.
BDD has a real sweet spot when it’s used to describe high level, user story-like business specifications. With MVC, this can be used to great effect when building out the controllers layer of your application. The controllers are what handle user actions and inputs to the application, determining what the correct result should be e.g. displaying a view, redirecting to another action. In a nutshell the controllers are what govern the flow of your application from the end users perspective, which makes them ideal candidates for BDD style specifications.
The out-of-the-box Asp.Net MVC project (File | New Project) comes with some pre-built controller functionality – the HomeController and the AccountController. This allows you to build a MVC application with user authentication very quickly and, although most people will choose not to use this code for anything other than a simple web app, it provides a set a business rules and functionality that everyone is familiar with.
So, to illustrate my point, I decided to use BDD to create a series of specifications for the out-the-box HomeController and AccountControlller functionality. Whilst this is a slightly backwards excercise as the functionality for these controllers already exists, hopefully this will show how you could continue in this way to build up the other controllers in your application.
Pre-Requisites
We’ve been using JP Boodhoo’s style of BDD specifications on my current project and whilst this has proved successful, during this time, JP has been adapting and pushing his style and syntax to something even more terse and fluent. He now has the developwithpassion library available up on GitHub which I’ve used for this excercise. It’s been a bit of a jump from the style and syntax that I’ve been using on my project, but I’m really happy with the results. I’ll post a fully working code sample at the end of the series with everything you need to run the specs.
First up – the HomeController.
If we imagine that the HomeController did not already exist, our first requirement would be that we needed something to handle the overall default action on the site – i.e. what happens when someone just browses to http://mysite.com ? We now know that we need a HomeController and that it’s default action should be to display the home page of the site. We also know (because it already exists) that our home page view should display a message – “Welcome to ASP.NET MVC!”.
So, our first scenario is:
When the home controller is told to display the default view
- It should display the home page view
- It should display the welcome message in the view
Translating this into code, I came up with the following specification
[Concern(typeof (HomeController))]
public class when_the_home_controller_is_told_to_display_the_default_view : observations_for_a_sut_without_a_contract<HomeController>
{
static string key;
static string message;
static ActionResult result;
context c = () =>
{
key = "Message";
message = "Welcome to ASP.NET MVC!";
};
because b = () =>
result = sut.Index();
it should_return_the_home_view = () =>
result.is_a_view_and().ViewName.should_be_empty();
it should_display_the_welcome_message_in_the_view = () =>
result.is_a_view_and().ViewData[key].should_be_equal_to(message);
}
The fluent syntax in the BDD specifications is mostly straight from JP’s developwithpassion library, which uses extension methods to the extreme to wrap MbUnit and RhinoMocks. I added a couple of MVC ActionResult specific extension methods to help with checking the results of the actions by casting them to their expected types first:
public static class ActionResultExtensions
{
public static ViewResult is_a_view_and(this ActionResult result)
{
return (result as ViewResult);
}
public static RedirectResult is_a_redirect_and(this ActionResult result)
{
return (result as RedirectResult);
}
public static RedirectToRouteResult is_a_redirect_to_route_and(this ActionResult result)
{
return (result as RedirectToRouteResult);
}
public static string controller_name(this RedirectToRouteResult redirect_result)
{
return redirect_result.RouteValues["Controller"].ToString();
}
public static string action_name(this RedirectToRouteResult redirect_result)
{
return redirect_result.RouteValues["Action"].ToString();
}
public static void should_be_empty(this String the_string)
{
the_string.should_be_equal_to(string.Empty);
}
}
I’m also relying on the convention over configuration approach of MVC in that if you don’t pass in a view name to an ActionResult then it will use the name of the calling action, so when I say:
it should_return_the_home_view = () =>
result.is_a_view_and().ViewName.should_be_empty();
I’m enforcing that the view name is the same as the action name.
We’ve now got the default action of the HomeController covered and if these specifications were executed, they would pass based on the out-the-box MVC project HomeController functionality:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
}
Our second scenario (which we know by looking at what the HomeController already does!) is that we need an about page. We (already) decided to handle this by adding an About action to the HomeController.
When the home controller is told to display the about view
- It should display the about page view
Simple really. Try to remember that normally, the HomeController would not already have this functionality so we would be driving this out from the spec. Translating this into code and following the conventions I had previously I came up with the following specification:
[Concern(typeof(HomeController))]
public class when_the_home_controller_is_told_to_display_the_about_view : observations_for_a_sut_without_a_contract<HomeController>
{
static ActionResult result;
because b = () =>
result = sut.About();
it should_return_the_about_view = () =>
result.is_a_view_and().ViewName.should_be_empty();
}
Ok, so this isn’t rocket science, and the HomeController doesn’t really do much, but if we wanted to add a help page for example then we could go on in similar fashion for adding the spec and then adding the action onto the HomeController. Or, if we decided to change the default home page view (e.g. to display the current date, or the logged in username, or a different view for every day of the week), then we can add to or modify our HomeController specifications accordingly.
Next Time…
I’m really happy with these controller specs and creating them for the HomeController, although simple, has given me a style and convention of how I’m going to approach the other functionality in our out-the-box MVC application. In the next post, I’ll start to look at the AccountController, where things get a bit more interesting. Hopefully this has shown how powerful BDD can be when talking at the controller level of an application.
UPDATE - Due to wierd formatting issues with the code snippets, I'd recommend reading this in Chrome, which seems to give the best experience!
@broomej