Test Driven Development with ASP.Net MVC (Part 4)

The series in full:

In Part 3 we got the first test up and running. Now we can get straight on with adding more tests and functionality to meet the needs of the User Story. A quick look at the Acceptance Criteria suggests the next functionality to implement:

Scenario 1 – Search criteria options are pre populated
GIVEN that I want to search for an event by region
WHEN I select to choose a region
THEN a list of possible regions is presented

I think at this point I would like to expend on the Acceptance Criteria again, to make it a bit more explicit.  You shouldn’t be afraid the make changes and clarification as you go, it is all part of the process.  So I will change Scenario 1 to:

Scenario 1 – Search criteria options are pre populated
GIVEN that I want to search for an event by region
WHEN I select to choose a region
THEN a list of possible regions is presented
AND the list should contain "All", "North", "South" and "London"
AND "All" should be the default value

This looks a little better to me.  It does also make me think about another Scenario that we need to cover but more about that later. But first, we can start with another test:

        [TestMethod]

        public void PresentAListOfAllPossibleRegionsForTheUserToSelectFrom()

        {

        }

The next part of my TDD process is to think about the behaviour of the functionality that the Controller will need to perform, and how it will achieve it within the architecture of the system. We need some data, and the application will have a service layer, so this is a good indication that we will need a service of some kind.  We also need to get some data to the view so are going to need a model of some kind. Lets fill in the test.

        [TestMethod]

        public void PresentAListOfAllPossibleRegionsForTheUserToSelectFrom()

        {

            //Arrange

            var mockEventService = new Mock<IEventService>();

            var response = new GetAllRegionsResponse();

            response.Regions = new List<RegionDto>();

            response.Regions.Add(new RegionDto {Id = 1, Name = “North”});

            response.Regions.Add(new RegionDto {Id = 2, Name = “South”});

            response.Regions.Add(new RegionDto {Id = 3, Name = “London”});

            mockEventService.Setup(x => x.GetAllRegions()).Returns(response);

            var eventController = new EventController(mockEventService.Object);

            //Act

            var result = (ViewResult)eventController.Search();

            var model = (SearchViewModel)result.Model;

            //Assert

            Assert.AreEqual(3,model.Regions.Count(),

                “Unexpected Number of Regions returned”);

        }

There is a bit going on in the test, so I will try to explain it.  Remember that the red text is Resharpers way of telling us that the implementation is missing, so there is lots of work to do to get this test up and running.  As we are defining the test first and them implementing to make it pass, we only implement the functionality we know we are going to need, not the functionality we think we may need in the future.

The first line is creating a mock for the IEventService. My mocking framework of choice for .Net is Moq. The next part is creating a Response object to be returned from the mock service when we call the GetAllRegions method.

mockEventService.Setup(x => x.GetAllRegions()).Returns(response);

is setting the behaviour of the mocked IEventService. It basically says: when the GetAllRegions method of the IEventService is called, return the Response I have described in the test, not the actual response from the real implementation of IEventService.

Now we are using a service we would expect it to get passed into the constructor of the Controller, IOC style. The assertion for this test will ensure the the 3 regions from the service call are populated on the model.

Now the test is complete we can start on the implementation. I tend to start at the top of the test and fill in the implementation as I go. My advice is to leverage the refactoring capabilities of ReSharper (or your tool of choice – even standard Visual Studio has tooling to take some of the pain away). As part of the implementation I am going to introduce Ninject to take care of the IOC duties.

The steps to complete the implement go a little something like this:

  • Add a new project for the services and related things
  • Setup Ninject for the IOC
  • Create and service contract and an empty service implementation
public interface IEventService
    {
        GetAllRegionsResponse GetAllRegions();
    }
public class EventService : IEventService
    {   
        public GetAllRegionsResponse GetAllRegions()
        {
            throw new NotImplementedException();
        }
    }
  • Create the Response and RegionDto
public class GetAllRegionsResponse
    {
        public List<RegionDto> Regions;
    }
public class RegionDto
    {
        public int Id { getset; }
        public string Name { getset; }
    }
  • Create the SearchViewModel
 public class SearchViewModel
    {
        private List<RegionDto> regions = new List<RegionDto>();

        public List<RegionDto> Regions
        {
            get { return regions; }
            set { regions = value; }
        }
    }
  • Update the Controller to use the new service
public class EventController : Controller
    {
        private readonly IEventService eventService;

        public EventController(IEventService eventService)
        {
            this.eventService = eventService;
        }

        public ViewResult Search()
        {
            var model = new SearchViewModel();

            model.Regions = eventService.GetAllRegions().Regions;

            return View(model);
        }
    }

So lets fire up the tests.  But wait a second there is a problem with the first test we did as the constructor is expecting an argument, but we are not giving it one.  It is an easy fix but teaches us something about the nature of TDD; that you can never assume that tests are written and then forgotten. As the functionality becomes more complex, you will often have to revisit tests as at the time they were based on the most simple implementation that would make the test pass, which has now evolved into something more complex.  We can change the test by adding in the mock for the EventService.

        [TestMethod]

        public void DisplayTheDefaultSearchEventView()

        {

            //Arange

            var mockEventService = new Mock<IEventService>();

            var eventController = new EventController(mockEventService.Object);

            //Act

            var result = (ViewResult)eventController.Search();

            //Assert

            Assert.IsTrue(String.IsNullOrEmpty(result.ViewName));

        }

Any copy and paste is usually an indication of the opportunity for refactoring, and this is no exception. We shall look at refactoring the tests after we have them working. Lets run the tests.

Part4 Test Results 1

Part4 Test Results 1

There is some good news and some bad news. The new test is passing, but now the old test is failing. The reason for this is that we are not supplying an behaviour for the mockEventService, so when the method is called it is returning a null Response and we are essentially asking for null.Regions. Again we can fix this easily by adding the setup for GetAllRegions to the first test.  This is the second duplication of code, so some refactoring of the test class is definitely on the cards.  A quick run of the test lets us know the change was successful.  Just before we refactor the test class, I had a thought that I want to make my second test a little stronger, so I added an additional Assertion.  It is not uncommon to make tests more specific in this way, as you uncover more about the behaviour you want to test for.

            Assert.AreEqual(1, model.Regions.FindAll(x => x.Name == “North”).Count,

                “Expected to find ‘North'”);

I could test for all three regions, but it felt a bit like overkill at this stage. Let’s run the tests.

Part4 Test Results 2

Part4 Test Results 2

That’s more like it. Let the refactoring commence. We can move out the duplication into a [TestInitialize] method to share the implementation amongst all the tests in the class. I have also added a [TestCleanup] method to run after each test has run. This contains a check to verify that all the behaviours specified in the mock setup have been run, so the test will fail if you expect it to do something that it does not do, even if all the Assertions pass. Here is the test class in full.

[TestClass]
public class WhenSearchingEventControllerShould
{
    private EventController eventController;
    private Mock<IEventService> mockEventService;
    private GetAllRegionsResponse response;

    [TestInitialize]
    public void Setup()
    {
        //Arrange
        mockEventService = new Mock<IEventService>();

        response = new GetAllRegionsResponse
            {
                Regions = new List<RegionDto>
                    {
                        new RegionDto {Id = 1, Name = "North"},
                        new RegionDto {Id = 2, Name = "South"},
                        new RegionDto {Id = 3, Name = "London"}
                    }
            };

        mockEventService.Setup(x => x.GetAllRegions()).Returns(response);

        eventController = new EventController(mockEventService.Object); 
    }

    [TestCleanup]
    public void Teardown()
    {
        mockEventService.VerifyAll();
    }

    [TestMethod]
    public void DisplayTheDefaultSearchEventView()
    {
        //Act          
        var result = eventController.Search();

        //Assert
        Assert.IsTrue(String.IsNullOrEmpty(result.ViewName));
    }

    [TestMethod]
    public void PresentAListOfAllPossibleRegionsForTheUserToSelectFrom()
    {  
        //Act
        var result = eventController.Search();
        var model = (SearchViewModel)result.Model;

        //Assert
        Assert.AreEqual(3,model.Regions.Count,
            "Unexpected Number of Regions returned");
        Assert.AreEqual(1, model.Regions.FindAll(x => x.Name == "North").Count,
            "Expected to find 'North'");
    }
}

You can see that the setting up of the Regions is tied to this test class. If we needed a test that had a different setup for the call to GetAllRegions, it may be an indication that the we are testing a different set of behaviours and that the new test should belong in a new test class.

So now we have a second Controller test, however there is still work to do to get the things up and running in an Outside In manner. Check out Part 5 to see how we Test Drive the Service method we are mocking in the Controller tests, and get it talking to the Repository.

The series in full:

Test Driven Development with ASP.Net MVC (Part 3)

The series in full:

In Part 2 we looked the feature we are going to implement, along with the behaviour it needs to exhibit. In this post we will quickly cover the architecture of the application, and then get on with the implementation.

System Architecture

A lot of the examples of using TDD with MVC are fairly simple and straightforward.  Whilst this keeps the implementation simple, I though that my example should try to reflect the real world a bit more, without going overboard.  In order to really show the outside in approach with vertical slices, I need more layers to slice through.

The “Crazy Events” application 

The application will have the following components:

  • UI  Layer- ASP.Net MVC3
  • Service Layer – For the example this will be a simple in-process service layer to hold the business logic.  It could just as easily be WCF Web Services.
  • Repository Layer – To encapsulate the data access.  For the example they provide access to in-memory data, but would more likely be talking to a database, maybe with a suitable ORM.

So, from the layers we should be able to implement test first by testing the Controller action, the services and an relevant repository methods that are appropriate for testing.  We could also test the MVC routes, but I will leave that for another post.

The starting point

I am going to start with an almost empty solution with just two projects, an empty MVC3 Project, and an empty test project.  I am going to use MSTest, but any Unit Testing framework could be used. You may already have a application template in place, with all the projects and dependencies for the layers and such already created. That’s fine too. I just want to show that TDD doesn’t have to be scary, even with an empty solution.

The first test…

Ok, so here we are, ready to start implementing the event search functionality.  Writing the first test can be hard, where do you start? I always try to start with the most simple test that will make progress towards the goal.

MVC offers us some help.  The URL routing in MVC can be very helpful in deciding where to go, as it too describes the behaviour of the web site.  If we were to navigate to the event search page, we may well navigate to http://www.crazyevents.com/event/search. This would translate to a Controller called Event with an Action called Search.  This looks like a good place to start.

The most straight forward first test would be to test that the search action on the event controller returns the default view as per the MVC conventions. Lets write the test:

 [TestClass]

    public class WhenSearchingEventControllerShould

    {

        [TestMethod]

        public void DisplayTheDefaultSearchEventView()

        {

            //Arrange

            var eventController = new EventController();

            //Act

            var result = (ViewResult)eventController.Search();

            //Assert

            Assert.IsTrue(String.IsNullOrEmpty(result.ViewName));

        }

    }

It is possible to tell from the above code that this test would fail. It is safe to say it will not even compile. Lets take this as the first “red” stage.

At this stage I just want to talk for a second about ReSharper. For those not familiar ReSharper is a Developer productivity tool that removes a lot of friction from working within Visual Studio. It supports lots of quick navigation and re factoring options, and generally just makes the whole experience a little more enjoyable. There are similar tools available, and it is possible to work without it.

The red text is ReSharper’s way of saying that something is missing. In this case it is suggesting that the EventController and Search method are missing, so lets create them.

Firstly I create the EventController. There a few ways to do this. I tend to use the ReSharper Action to create the class, which will create the class in the same file. It doesn’t matter for now that the controller is in the wrong place, it can be moved easily later. If you prefer you could create the file in the correct place (in the Controller folder of the MVC Project) and add the necessary Using statement. Remember we need to add just enough code to get the test up and running, so the next step is to add the Search method on the Controller. The file can be moved to its rightful home if you haven’t done so already.

using System.Web.Mvc;

namespace CrazyEvents.Controllers

{

    public class EventController : Controller

    {

        public ViewResult Search()

        {

            return View();

        }

    }

}

There is only one last thing to sort out, we are missing a view. So I create one now. It will be very simple, and will not be Strongly Types at this stage, as we do not yet know what Model it will use.

@{

    ViewBag.Title = “Search”;

}

<h2>Search</h2>

I know, not much to look at yet, but remember this is just one of several steps to get us up and running from a standing start. A quick run of the test shows us we are on the right track. Notice ow the test class and test method name make a readable behavioural statement.

Unit Test Session

One down, many to go…

So we now have a functioning, albeit a fairly useless MVC application. We can always run it, just to make sure everything is okay. A quick addition to the route is required to register our new functionality. I will add it to the ‘root’ route for now, so the search page loads by default. It can always be changed later.

routes.MapRoute(“Root”, string.Empty, new { controller = “Event”, action = “Search” });

Running the app will result in a fairly uninspiring page. In the next post I will continue to build up the functionality one test at a time.

The series in full:

Test Driven Development with ASP.Net MVC (Part 2)

The series in full:

In Part 1, I talked about my view on TDD, and the particular flavour I use when doing TDD with MVC. In this post I am going to have a look at the feature we want to implement.

An event booking system

The functionality we are building is for an event booking system.  There is a database full of events, and people can use the web site to search for events and make a booking.

We are interested in implementing the search functionality. We may be fortunate to have analysts, testers and developers working together to craft the user stories with the product owner, but at this stage it doesn’t really matter how the user stories were created.  What matters is that there is a framework of ideas to build from.  They don’t have to be perfect, but it is difficult for the system to emerge without something to kick-start the ideas.

We have the following user story:

As a potential event attendee, I want to search for events, so that I can see all the available events that match my criteria

We also have some screen mock-ups to guide us.  A couple of the more relevant ones are shown below:

Event Search Screen mock-up 1

Event Search Screen mock-up 1

Event Search Screen mock-up 2

Event Search Screen mock-up 1

From the mock-ups, what behaviour do we want to cover? There are lots of potential scenarios, but lets focus on a few of the more straight forward ones for now:

• Search criteria options are pre populated
• Search for an event by region
• Search for an event by name
• No results returned from search
• Search criteria is ‘remembered’ when the results are shown

You will see that some of the scenarios overlap. For example, having no search results returned could well be part of searching for an event by name or region, however I find it better to cover the scenarios explicitly for the benefit of test separation in the case of both unit and acceptance testing.

From the scenario list we can now flesh out some examples to use:

Scenario 1 – Search criteria options are pre populated
GIVEN that I want to search for an event by region
WHEN I select to choose a region
THEN a list of possible regions is presented

Scenario 2 – Search for an event by region
GIVEN the following events exist

Event Code Event Name Region Description
CR/0001 Crochet for Beginners North A gentle introduction into the art of crochet
CR/0002 Intermediate Crochet North Taking your crochet to the next level
CH/3001 Cat Herding London A starter session for the uninitiated

WHEN I search for events in the “North” Region
THEN the search results present the following events

Event Code Event Name Region Description
CR/0001 Crochet for Beginners North A gentle introduction into the art of crochet
CR/0002 Intermediate Crochet North Taking your crochet to the next level

Scenario 3 – Search for an event by Name
GIVEN the following events exist

Event Code Event Name Region Description
CR/0001 Crochet for Beginners North A gentle introduction into the art of crochet
CR/0002 Intermediate Crochet North Taking your crochet to the next level
CH/3001 Cat Herding London A starter session for the uninitiated

WHEN I search for events with the name “Cat Herding”
THEN the search results present the following events

Event Code Event Name Region Description
CH/3001 Cat Herding London A starter session for the uninitiated

Scenario 4 – No Search results return from search
GIVEN the following events exist

Event Code Event Name Region Description
CR/0001 Crochet for Beginners North A gentle introduction into the art of crochet
CR/0002 Intermediate Crochet North Taking your crochet to the next level
CH/3001 Cat Herding London A starter session for the uninitiated

WHEN I search for events in the “South” Region
THEN I am informed that no search results have been returned

Scenario 5 – Search criteria is ‘remembered’ when the results are shown
GIVEN I have performed a search in the "North" region
WHEN the search results page is displayed
THEN the search criteria displays my choice of region

In Part 3 we will start implementing the application, guided by the behaviour defined in the scenarios.

The series in full:

Test Driven Development with ASP.Net MVC (Part 1)

In this series of post I am going to talk about how I implement systems in Asp.Net MVC using TDD.  It maybe a little different to some other TDD examples as I am also going to talk a bit about the supporting activities, and the wider thought processes involved.

The series in full:

My thoughts on TDD

A lot of words in books and articles have already been written about TDD, and I am assuming that most readers will have some knowledge of at least the theory behind it. To reiterate the basics:

  • Write a test
  • Watch the test fail
  • Make the tests pass with the most simple implementation possible
  • Write another test
  • Repeat (Red, Green, Refactor)

Software is built through lots of iterations of first failing then passing unit tests. TDD becomes less about testing and more about design as the system is driven out by the fulfilment of tests.

But even for people familiar and competent with the canonical examples of TDD, using TDD together with a more complex multi layered application can be daunting.  Most modern systems fall into that category, and I will attempt to use an example that is a bit more like the situation you may find yourself in in real life.

One of the things I have experienced when using TDD is that every practitioner has their own style, their own ‘flavour’ and dialect for their tests.  I don’t think that there are any right or wrong ways to do TDD, as long as the overall goals are achieved:

  • The design of the application is emergent
  • The logic is exercised accordingly
  • The tests provide confidence when refactoring or adding new functionality
  • The code is inherently testable, through the application of good design principles and practices

My own flavour of TDD for MVC is guided by the flowing principles:

  • Behavioural in nature – When embarking on development it is vital to understand the behaviour of the thing you are going to build.  For this reason, it should be specified in a suitable manner.  My preference is for user stories, with a strong set of acceptance scenarios (I tend to use the Given When Then format) and screen mock ups where possible (Balsamiq is my choice). The tests can then focus on meeting the behavioural needs, not just the technical ones.
  • Vertical Slices – I prefer to work through vertical slices of functionality, so that the User Story and scenarios relate to a discrete piece of functionality that can be tested and demonstrated as a whole.  This is very different from the traditional approach of working through the layers of an application layer by layer.
  • Outside in – I like to start at the outside of an application and work inwards.  Where the “outside” of the application is depends on your approach. For this example I am going to treat the Controller as the “outside” and work downwards towards the data layer.  Other approaches can see the UI layer (the view in MVC) marking the “outside” of the application.  I will discuss how these two approached differ in a short while.
  • Descriptive Naming – I aim to have the test names read as complete sentences that descript the behaviour on test.  It is not an exact science and often undergoes some changes as the tests progress.

Isn’t this Behaviour Driven Development?

People who are familiar with BDD will instantly recognise some of the terminology, and certainly my approach borrows heavily from the BDD movement.  I am reluctant to call this approach BDD for a few reasons:

  • I am not testing the true behaviour of the website from the UI itself; rather I am testing how the controller fulfils the behavioural needs of the UI.  A small difference, but an important one.  A true BDD approach would be to drive the UI with a tool like WatiN  in conjunction with a BDD specific tool like SpecFlow, which can be used as way to test at a feature level, even without having the tests drive the UI, but I think this weakens the overall effect.
  • Although I am focussed on behaviour, the tests are still unit tests (albeit with behavioural names).  The tests are still in the technical domain and do not abstract away all the technical details. Again, a true BDD approach would see the feature tests specified by their behaviour, not their technical specification. Once again BDD tools help with this, but it can add more weight to the process.
In Part 2 the discussion moves to the feature that needs to be implemented.

The series in full: