Asp.Net MVC Controllers + BDD = The perfect match? Part 2: Log On and Log Off with the AccountController

Tags: , , , , , , , No Comments »

This is part 2 in a series of posts on using Behaviour Driven Development to build and test your MVC controllers. The full series is as follows:

In my last post I talked about why I thought that BDD was a great choice when it comes to testing your MVC controllers. The controllers in an MVC application are what handle the user actions and inputs and determine what the correct result should be e.g. displaying a view or redirecting to another action. The controllers are therefore what govern the flow of your application from the end user’s perspective, which makes them ideal candidates for BDD style specifications as the language and business requirements are relevant to and can be validated by a non-technical team member (e.g. the Product Owner).

I began by looking at the out-the-box Asp.Net MVC application that you get when you select “File | New Project | ASP.Net MVC Web Application” in Visual Studio as this provides us with some basic, yet widely understood functionality that I can use to illustrate my point. I started by writing specifications for the out-the-box HomeController to prove that it returned the correct views for the Index and About actions, and that the correct data was passed into the ViewData.

As I mentioned before, this exercise is slightly backwards, as the functionality we are writing specifications for already exists, however, the intention is to show how beneficial BDD can be at this layer of the application and how it could be applied going forward as you build out the rest of your functionality.

Recap

I’m using JP Boohoo’s developwithpassion BDD library to give me a style and syntax for my BDD specs. If you’re new to BDD, I’d recommend checking out his blog first for some background info on what this library is all about.

I’d also created some MVC specific extension methods for testing my HomeController so that I can easily prove the type of ActionResult my actions return and check the type-specific properties on them in a fluent syntax e.g.

it should_return_the_home_view = () =>
    result.is_a_view_and().ViewName.should_be_empty();

The AccountController

The AccountController is more interesting than the HomeController as it actually does something! If we take a look at the out-the-box functionality it contains, we can see that it provides the following account operations:

  • Log on a User
  • Log off a User
  • Register a new User
  • Change a User’s password
  • Prevent Window’s authenticated Users from accessing the application

So, I’ll start in this post by dealing with the Log on and Log off functionality.

Set up

The out-the-box AccountController provides two constructors, which makes it easy to test. It has two dependencies – a FormsAuthenticationService and a MembershipService, which at runtime will be instantiated using the default forms authenticaiton and membership providers. However, during testing, we can set these to be whatever we want. Our AccountController specifications are all going to use mock versions of these dependencies, which will allow us to set up and simulate the contexts that we need to fulfil our specifications. I start by creating a “base” context that can be used by all my AccountController specifications:

public abstract class concern_for_account_controller : observations_for_a_sut_without_a_contract<AccountController>
{
    protected static IFormsAuthentication forms_authentication;
    protected static IMembershipService membership_service;
    protected static string user_name;
    protected static string password;
    protected static bool remember_me;
    protected static string return_url;
    protected static string email;
    protected static string confirm_password;
    protected static string new_password;
    protected static string confirm_new_password;

    context c = () =>
    {
        forms_authentication = the_dependency<IFormsAuthentication>();
        membership_service = the_dependency<IMembershipService>();
        membership_service.Stub(ms => ms.MinPasswordLength).Return(4);
        user_name = "name";
        password = "password";
        confirm_password = "password";
        new_password = "newpassword";
        confirm_new_password = "newpassword";
        email = "email";
        remember_me = false;
        return_url = "/";
    };
}

The developwithpassion BDD library will take care of a lot of set up work for us. I initialise the IFormsAuthentication and IMembershipService using the the_depdendency() method, which tells the BDD library to create a new mock version of each of these interaces using RhinoMocks, and also to use them in the constructor of the system under test (in this case, the AccountController). So, whenever I access the system under test in one of my specifications, it will already be created and have these mock instances as the dependencies that the AccountController needs.

Log On

This first set of specifications I created covers the two LogOn actions. The first is a GET request action which simply displays the Log On view. The second only accepts POST requests and performs the actual log on, providing everything is valid. Here’s the out-the-box functionality that we’re going to be testing:

public ActionResult LogOn()
{

    return View();
}

[AcceptVerbs(HttpVerbs.Post)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
    Justification = "Needs to take same parameter type as Controller.Redirect()")]
public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
{

    if (!ValidateLogOn(userName, password))
    {
        return View();
    }

    FormsAuth.SignIn(userName, rememberMe);
    if (!String.IsNullOrEmpty(returnUrl))
    {
        return Redirect(returnUrl);
    }
    else
    {
        return RedirectToAction("Index", "Home");
    }
}

Remember – this is the out-the-box Asp.Net MVC functionality that we get with a new project. Working backwards from this functionality, I came up with the following set of specifications…

1. The first action (for a GET request) is pretty simple – we’re just proving that the return is a ViewResult and that the view name is the same as the action name (in this case “LogOn”) by relying on the convention over configuration approach of Asp.Net MVC. If we don’t supply a specific view name for a view result, then the framework will look for one with the same name as the action.

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_display_the_log_on_view : concern_for_account_controller
{
    static ActionResult result;

    because b = () =>
        result = sut.LogOn();

    it should_display_the_log_on_view = () =>
        result.is_a_view_and().ViewName.should_be_empty();
}

2. We then need to test the “happy day” scenario. What happens when everything goes as expected? In this case, we validate the user successfully, log the user on successfully and redirect to where they came from. We can override the base context and tell the membership service that we we ask it to validate the user it will return a successful response.

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_log_on_a_user : concern_for_account_controller
{
    static ActionResult result;

    context c = () =>
        membership_service.Stub(ms => ms.ValidateUser(user_name, password)).Return(true);

    because b = () =>
        result = sut.LogOn(user_name, password, remember_me, return_url);

    it should_validate_the_users_credentials = () =>
        membership_service.was_told_to(ms => ms.ValidateUser(user_name, password));

    it should_log_the_user_on_to_the_system = () =>
        forms_authentication.was_told_to(fa => fa.SignIn(user_name, remember_me));

    it should_redirect_the_user_back_to_where_they_came_from = () =>
        result.is_a_redirect_and().Url.should_be_equal_to(return_url);
}

3. Next, we can change the context slightly, this time to blank out the return url. This simulates the user logging on without browsing any other part of the site first. In this case, we validate and authenticate the user in the same way, but we redirect to the home page.

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_log_on_a_user_and_they_havent_come_from_anywhere : concern_for_account_controller
{
    static ActionResult result;

    context c = () =>
    {
        return_url = string.Empty;
        membership_service.Stub(ms => ms.ValidateUser(user_name, password)).Return(true);
    };

    because b = () =>
        result = sut.LogOn(user_name, password, remember_me, return_url);

    it should_validate_the_users_credentials = () =>
        membership_service.was_told_to(ms => ms.ValidateUser(user_name, password));

    it should_log_the_user_on_to_the_system = () =>
        forms_authentication.was_told_to(fa => fa.SignIn(user_name, remember_me));

    it should_redirect_the_user_to_the_home_page = () =>
    {
        result.is_a_redirect_to_route_and().controller_name().should_be_equal_to("Home");
        result.is_a_redirect_to_route_and().action_name().should_be_equal_to("Index");
    };
}

4. Then we need to start checking our error case scenarios – what happens if we try to log on without specifying a username?

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_log_on_a_user_with_no_username : concern_for_account_controller
{
    static ActionResult result;

    context c = () =>
        user_name = string.Empty;

    because b = () =>
        result = sut.LogOn(user_name, password, remember_me, return_url);

    it should_display_the_log_on_view = () =>
        result.is_a_view_and().ViewName.should_be_empty();

    it should_display_the_username_required_validation_message_in_the_view = () =>
        result.is_a_view_and().ViewData.ModelState["username"].should_not_be_null();
}

5. And the same for password:

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_log_on_a_user_with_no_password : concern_for_account_controller
{
    static ActionResult result;

    context c = () =>
        password = string.Empty;

    because b = () =>
        result = sut.LogOn(user_name, password, remember_me, return_url);

    it should_display_the_log_on_view = () =>
        result.is_a_view_and().ViewName.should_be_empty();

    it should_display_the_password_required_validation_message_in_the_view = () =>
        result.is_a_view_and().ViewData.ModelState["password"].should_not_be_null();
}

6. If the user does supply a username and password then we need to try to validate it via the membership service. The credentials they’ve supplied may be invalid, which we can simulate by stubbing out the response from the membership service:

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_log_on_a_user_who_does_not_exist : concern_for_account_controller
{
    static ActionResult result;

    context c = () =>
        membership_service.Stub(ms => ms.ValidateUser(user_name, password)).Return(false);

    because b = () =>
        result = sut.LogOn(user_name, password, remember_me, return_url);

    it should_try_to_validate_the_users_credentials = () =>
        membership_service.was_told_to(ms => ms.ValidateUser(user_name, password));

    it should_display_the_log_on_view = () =>
        result.is_a_view_and().ViewName.should_be_empty();

    it should_display_the_invalid_username_validation_message_in_the_view = () =>
        result.is_a_view_and().ViewData.ModelState["_FORM"].should_not_be_null();
}

Log off

7. Finally, for completeness, we can test the expected behaviour for logging off a user, which tells the authentication to sign the user out and then redirects back to the home page:

[Concern(typeof(AccountController))]
public class when_the_account_controller_is_told_to_log_off_a_user : concern_for_account_controller
{
    static ActionResult result;

    because b = () =>
        result = sut.LogOff();

    it should_log_the_user_out_of_to_the_system = () =>
        forms_authentication.was_told_to(fa => fa.SignOut());

    it should_redirect_the_user_to_the_home_page = () =>
    {
        result.is_a_redirect_to_route_and().controller_name().should_be_equal_to("Home");
        result.is_a_redirect_to_route_and().action_name().should_be_equal_to("Index");
    };
}

Output

If I run my specifications through the Gallio Icarus test runner I can see that they are being executed successfully:

But more interestingly, I can use JP’s bdddoc report generator to produce an easy to read, BDD specification report based on my specifications in code:

Summary

This is where the real benefit of BDD comes in. My specifications in code are understandable and easily readable when ran through a simple report parser. They can be discussed and validated by developers and non-developers alike. I could show this to the guys behind Asp.Net MVC and see if I’ve understood their desired behaviour of the out-the-box AccountController! If not, we figure out what’s wrong or missing and carry on. At this level of the application the specs really add value in the context of the business requirements of your system. This is why I think that BDD is a great technique for testing the controllers of your MVC application.
In the next post I’ll look at the remaining functionality in the AccountController. I’ll be posting a full working code sample of these specs at the end of the series.

Asp.Net MVC Controllers + BDD = The perfect match? Part 1: The HomeController

Tags: , , , , , , , 1 Comment »

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.

Design by j david macor.com.Original WP Theme & Icons by N.Design Studio