Using Behaviour-Driven Development with ASP.NET MVC – Part 2

In part one, I introduced the PlaylistShare project, created some initial user stories, defined the acceptance criteria and created a template for writing executable specifications. In this part, I am going to write some executable specs for viewing playlists and implement the behaviour to make them pass. You can download the source code at the end of this post.

There a couple of approaches you can take when writing specs for an ASP.NET MVC user interface. Either you can run them directly against the UI from inside a browser, using a tool such as WaitiN. Or, you can drop in just below the UI and run the specs against the controllers. I prefer running them against the controllers as they run much faster and are not susceptible to changes to the HTML elements. However, you may find some aspects are better specified using a UI testing tool, such as behaviour that relies heavily on client-side scripting, which the controllers know nothing about.

I will be starting with the executable specification template I created in part one:

namespace view_playlist_specs
{
    namespace scenario_of
    {
        public abstract class listener_views_playlists : Specification<PlaylistController>
        {
            protected override PlaylistController create_subject()
            {
                // Create the system under test
                return new PlaylistController();
            }
 
            protected void given_a_list_of_playlists()
            {
                // Establish a list of playlists
            }
        }
    }
 
    namespace viewing_a_list_of_playlists
    {
        [TestFixture]
        public class when_the_listener_views_the_playlists 
            : scenario_of.listener_views_playlists
        {
            protected override void setup_scenario()
            {
                // Arrange
                given_a_list_of_playlists();
            }
 
            protected override void execute_scenario()
            {
                // Act
            }
 
            [Test]
            public void should_display_a_list_of_playlists()
            {
                // Assert
            }
 
            [Test]
            public void playlists_should_be_ordered_by_date_submitted_from_newest_to_oldest()
            {
                // Assert
            }
        }
    }
}

The first scenario we are going to implement is:

Story: Listener views playlists

As a playlist listener, I can view a list of playlists that have been submitted, so that I can find a playlist I might enjoy.

Scenario 1: Viewing a list of playlists

Given a list of playlists

When the listener views the playlists

Then should display a list of playlists

  And playlists should be ordered by date submitted from newest to oldest

First, lets create a new ASP.NET MVC application and add a PlaylistController to give us a subject to work with.

[HandleError]
public class PlaylistController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Now that we have a controller, let’s specify the first outcome of the scenario.

[Test]
public void should_display_a_list_of_playlists()
{
    the_view_model.should_be_equivalent_to(playlists);
}

The outcome states that a list of playlists should be viewed on the page.

the_view_model is a property on the spec base class that wraps calls to ProductsController.ViewData.Model. This makes the code slightly more readable. We can use ViewData.Model on the controller to pass data back to the view. In this case we want to pass back a list of Playlist objects.

You will notice I have used a method called should_be_equivalent_to to verify that the_view_model contains the same elements as playlists. This is an extension method defined in a SpecificationExtensions class I have added to the project. This class contains some helpful extension methods that wrap-up calls to NUnit assertions. This enables us to assert conditions directly on the objects, producing slightly more readable outcomes. Again, this is a personal preference and you can use ordinary Assert calls if you wish.

Running this spec should result in a failure, as we are not yet returning any playlists from the Index() controller method. We need to provide the controller with our list of playlists. To do this, we need to somehow pass a list of Playlist objects to the controller. The best way to do this is to use Dependency Injection and Mocking to pass a Repository containing our Playlist objects into the controller. Woah! Sounds complicated. But, it’s actually quite simple…

Let’s modify the PlaylistController to take an IPlaylistRepository and initialise a field in the constructor.

[HandleError]
public class PlaylistController : Controller
{
    private readonly IPlaylistRepository _playlistRepository;
 
    public PlaylistController(IPlaylistRepository playlistRepository)
    {
        _playlistRepository = playlistRepository;
    }
 
    public ActionResult Index()
    {
        return View();
    }
}

Now we need to pass an instance of this repository when we create the PlaylistController in our specs. To do this I am going to generate a stub using RhinoMocks. This creates a runtime implementation of the IPlaylistRespository that we can pass into the controller.

protected override void setup_scenario()
{
    playlist_repository = MockRepository.GenerateStub<IPlaylistRepository>();
}
 
protected override PlaylistController create_subject()
{
    return new PlaylistController(playlist_repository);
}

The setup_scenario method gets called right before create_subject, so we can use this to create any dependencies before the PlaylistController gets instantiated. Any scenarios can create additional context by overriding the setup_scenario method.

Now we need to add a GetAll() method to IPlaylistRepository that allows the PlaylistController to retrieve a list of Playlist objects.

public ActionResult Index()
{
    var playlists = _playlistRepository.GetAll();
    return View(playlists);
}

Back in our specs, we can now tell the stubbed playlist_repository to return some Playlist objects whenever the GetAll() method is called.

protected void given_a_list_of_playlists()
{
    playlists = new List<Playlist>
                    {
                        new Playlist(),
                        new Playlist(),
                        new Playlist()
                    };
    playlist_repository.Stub(r => r.GetAll()).Return(playlists.ToArray());
}

Our spec should now be passing! We have now confirmed the controller is passing a list of playlists to the view to display on the page.

So, let’s write an assertion for our next outcome: “playlists should be ordered by date submitted from newest to oldest”. Here we verify that the Playlist objects sent to the view are ordered by date-submitted in a descending order.

[Test]
public void playlists_should_be_ordered_by_date_submitted_from_newest_to_oldest()
{
    the_view_model.should_be_ordered_by_descending(p => p.DateSubmitted);
}

For this to compile we need to add a DateSubmitted property to the Playlist object.

I have created a helper method called should_be_ordered_by_descending that will verify that the playlists are ordered correctly. It’s really useful to create extension methods for complicated assertions to better explain what is being verified.

We can now make this spec pass by using LINQ to order the list of Playlist objects in the controller action before they are returned to the view.

public ActionResult Index()
{
    var playlists = _playlistRepository.GetAll();
    return View(playlists.OrderByDescending(p => p.DateSubmitted));
}

Here is the output from the Resharper test runner:

test-run-resharper-1-small

Using these naming conventions means that if a spec fails, it’s easy to identify what behaviour is causing the problem. You also get clear, concise, executable documentation that describes how your application behaves.

You might have noticed that I have been implementing the playlist controller without even running the web site. By implementing the controller first, we can focus on the core behaviour of the application without being concerned about how it is presented. In the next part, we will create the view that displays the playlists on the page.

Download the source code for Part 2

Advertisements

8 Responses to “Using Behaviour-Driven Development with ASP.NET MVC – Part 2”


  1. 1 Arnis L. May 13, 2009 at 10:01 am

    This convention is quite brilliant. 🙂

  2. 2 timross May 26, 2009 at 10:31 pm

    Thanks Arnis, glad you like it. I have changed my mind on things slightly recently. Mainly related to the separation between business-focused features and object-focused specs, which I describe here. I will be posting a follow-up to this soon.

  3. 3 Rajesh Pillai August 17, 2009 at 12:44 pm

    I liked this series 🙂
    Hope to see more on this stuff. I had done some TDD but this BDD approach seems more natural and the concepts of specification as opposed to test changes the mindset as to how you approach the development as a whole and you have a beautiful byproduct in the process i.e the specs. Really great.

  4. 4 timross August 17, 2009 at 2:28 pm

    Thanks for your comment, Rajesh. Yes, it’s about time I posted the next instalment in this series! I have recently been using Cucumber to drive the outer-level scenarios and NUnit to write the object-level specs. I plan to discuss this in the next post.

  5. 5 Vickus September 29, 2009 at 8:50 am

    Thanks for this introduction.Very helpful.


  1. 1 Reflective Perspective - Chris Alcock » The Morning Brew #344 Trackback on May 11, 2009 at 9:58 am
  2. 2 DotNetBurner - ASP.net MVC Trackback on May 12, 2009 at 4:56 pm
  3. 3 Features and Specs « Tim Ross – .NET Developer Trackback on May 19, 2009 at 1:39 pm

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s





%d bloggers like this: