Welcome to EMC Consulting Blogs Sign in | Join | Help

James Broome Blog

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

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:

GallioResults 

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:

 SpecReport

 

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.

 

Bookmark and Share
Published Wednesday, September 23, 2009 12:01 PM by james.broome

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Eric Duncan said:

I have been looking for other BDD frameworks, and you gave the closest thing I am fimilar to with Boohoo's syntax.

I see now that Machine.Specifications (or MSpec) has been extended to support Boo's syntax.  Very nice!

I am still wrapping my head around Mspec, but MSpec.Boo is next up afterwards.  Thanks for the post!

October 6, 2009 5:13 PM
 

james.broome said:

Cheers Eric,

I've been using JP Boodhoo's style of BDD specs for over a year now, since I attended his Nothin But .Net training course which had a heavy BDD focus. I'm also planning on spending some time looking at other options as the frameworks seem to be a lot more mature than they used to be a year or so ago so watch this space for how I get on...

James

October 6, 2009 5:25 PM
 

alberto said:

"when the account controller is told to log off a user" doesn't sound like a bussiness concern to me. It should be something along the lines "when a user logs off" instead.

October 9, 2009 12:51 AM
 

Samnang said:

I see the way you organize your tests in BDD style, it provides both documentation and features that match business requirement. But I see in your tests you have mocks/stubs some of your services, so when do you test that path and persistence layer? do you test it in separate project?

Samnang  

October 9, 2009 6:04 AM
 

james.broome said:

@alberto - yeah I know what your saying, however, by not including the system under test (SUT) in your context (in this case the account controller) you lose focus of what it is you are writing specifications for. So with the context "when a user logs off", there could be a whole load of expected behaviour that has nothing to do with the account controller and therefore it makes it harder to know what to test in this scenario.

By concentrating on the SUT, you drive out the expected behaviour of the SUT:

"When the SUT is told to do something, it should do something"

The specifications are then focussed on the particular SUT so you can drive out its behaviour in isolation.

One thing I keep coming back to is the use of the word "controller" in these specs - like you say, its not immediately apparent what that means to a non-techy person. If you have a better suggestion I'd love to hear it, otherwise the compromise I would make is to allow that term to creep into the domain of the business - a controller is a part of the system that is responsible for a particular set of related functionality, and make sure everyone understood what it meant.

@Samnang - yep, by working test-first in this way I can drive out the behaviour of the AccountController without needing to build out its dependencies. The use of mocks allows me to do this, setting up the expected behaviour for each scenario. However, once I finished building out the Account Controller I'm far from having a fully working system - what it has told me though is the dependencies I know I need, so I would then move on to writing specifications for the dependencies. The use of interfaces and mocks allows you to concentrate on each SUT one at a time however you're gonna need to build each thing before you've finished your app.

So, yes I would move on to writing specs for the services etc that the account controller uses. I like having a specs file for each class and sometimes keep them side by side in the same project, sometimes have seperate test projects, thats just down to personal preference.

Hopet this helps

James

October 9, 2009 9:49 AM
 

sandeep said:

As a professional Industrial Training Provider IT Training Hub offer Dot net Training, Php Training, Seo Trainng, SEM training, Web designing Training, , Web Development Training, Ethicial Hacking Training in delhi NCR.

September 3, 2010 11:11 AM

Leave a Comment

(required) 
(optional)
(required) 
Submit

About james.broome

James is a Technical Consultant at Conchango
Powered by Community Server (Personal Edition), by Telligent Systems