BDD with SpecFlow, NUnit and MVC3–View a List Of Products

In the last post I set up an MVC project and Specs project and added various assembly references to the Specs project. In this post I’ll write my first feature which is to view a list of products.

The First Feature

Add a features folder to the Specs project then add a new item.

image

If you have installed SpecFlow correctly you should be able to add a SpecFlow feature. I will call mine ViewAListOfProducts.feature. I am not sure if this is the right level of granularity for a feature but ManageProducts sounds a bit too wide. I can always refactor later if this proves wrong. I am also unsure how to best organize my features in folders but will start with one folder for features and reorganise later if necessary.

Next I need to detail the feature, this is usually in the format:
Feature: feature
    In order gain a benefit
    As a user
    I want feature

In my case it seems reasonable to do:
Feature: View a list of products
    In order to see what products there are
    As a user
    I want to view a list of products

The First Scenario

Then comes the first scenario, ‘Display the default products view’, and where I run into naming trouble. I decided that I would start testing at the controller rather than the interface so, as much as I would like to write out the scenario as:
Scenario: Display the default products view
    When I navigate to /product/
    Then the products page should be displayed
    And the page should contain a list of products

That sounds misleading as I am not navigating to a URL or displaying a page. I’ll use a more honest description:
Scenario: Display the default products view
    When the product controller is told to display the default view
    Then the controller should return a view
    And the view title should be products
    And the view should contain a list of products

This better describes what I want to do but is less easily understood by any non technical stakeholder (which is one of the benefits of BDD). I think it is probably better than the alternatives in my case which are either testing the GUI or a misleading scenario.

Compile and Run

Specflow creates a set of tests when the project is compiled which can then be run in the normal test runner. I use Resharper and the results are:

image

The tests are ignored but the output of the tests provides the code you need to write the steps file.

The Steps

Add a Steps directory to the Specs project and add a new item. again if Specs is installed correctly you should be able to add a SpecFlow Step Definition. I’ll call mine ViewAListOfProducts.cs.

Adding the code output from the test run gives results in this:

[Binding]
public class ViewAListOfProducts {
    [When(@"the product controller is told to display the default view")]
    public void WhenTheProductControllerIsToldToDisplayTheDefaultView() {
        ScenarioContext.Current.Pending();
    }

    [Then(@"the controller should return a view")]
    public void ThenTheControllerShouldReturnAView() {
        ScenarioContext.Current.Pending();
    }

    [Then(@"the view title should be products")]
    public void ThenTheViewTitleShouldBeProducts() {
        ScenarioContext.Current.Pending();
    }

    [Then(@"the view should contain a list of products")]
    public void ThenTheViewShouldContainAListOfProducts() {
        ScenarioContext.Current.Pending();
    }
}

I can then code WhenTheProductControllerIsToldToDisplayTheDefaultView to the simplest thing that works:

[When(@"the product controller is told to display the default view")]
public void WhenTheProductControllerIsToldToDisplayTheDefaultView() {
    ProductController controller = new ProductController();
    ActionResult result = controller.Index();
}

Of course now I need to create the controller which I will do with the MVC scaffolding. Although I don’t need it yet, I know I’ll eventually need the CRUD methods so will scaffold them all.

Running the tests after scaffolding shows the first method as done and the rest as pending. The second method, ThenTheControllerShouldReturnAView, is easy enough. Moving the result declaration out to the class allows:

[Then(@"the controller should return a view")]
public void ThenTheControllerShouldReturnAView() {
    _result.ShouldBeType(typeof (ViewResult));
}


This is the first time I have used the Should framework and I really like the readability it gives the tests. Running the tests now shows the first two methods as done. I guess I could force a failure on the type test to get a red light first but that would seem like moving backwards to go forwards.

The remaining methods are as easy to complete and the final class looks like this.

[Binding]
public class ViewAListOfProducts {
    [When(@"the product controller is told to display the default view")]
    public void WhenTheProductControllerIsToldToDisplayTheDefaultView() {
        var controller = new ProductController();
        _result = controller.Index();
    }

    [Then(@"the controller should return a view")]
    public void ThenTheControllerShouldReturnAView() {
        _result.ShouldBeType(typeof (ViewResult));
    }

    [Then(@"the view title should be products")]
    public void ThenTheViewTitleShouldBeProducts() {
        ViewResult.ViewData["Title"].ShouldEqual("Products");
    }

    [Then(@"the view should contain a list of products")]
    public void ThenTheViewShouldContainAListOfProducts() {
        ViewResult.ViewData.Model.ShouldBeType(typeof (List<Product>));
    }


    private ActionResult _result;

    private ViewResult ViewResult {
        get { return (ViewResult) _result; }
    }
}

Getting these tests to pass does not seem to drive out much code:

public ActionResult Index() {
    ViewData["Title"] = "Products";
    return View(new List<Product>());
}

Plus, of course an empty Product class. It is the simplest code to make the tests pass but at this stage it doesn’t seem the most efficient way to write code.

The next stage will be to expand our specs and code and hopefully it will drive the design down to the data layer and prove a bit more efficient.

Leave a Reply