BDD with SpecFlow, NUnit and MVC3–Refactored

Whilst the previous post (BDD with SpecFlow, NUnit and MVC3–More View a List Of Products) got the job done, it could have done it a lot better.

After a conversation on Twitter and in the comments on the previous post with Darren Cauthon (@darrencauthon, http://www.cauthon.com) and Marcus Hammarberg (@marcusoftnet, http://www.marcusoft.net), there are three areas where I can refactor to improve the tests:

1 Language

In an earlier post I was torn between ‘honest’ language and non-technical language:

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.

I have now swung to the non-technical viewpoint and rewritten my scenario to:

Scenario: Visit the product pageWhen I visit the product pageThen I should see the Index viewAnd the view title should be ProductsAnd the view should contain a list of products

It’s then just a case of replacing the tags on the steps and, because I’m a neat freak, renaming the methods to match the tags.

2 State in Class Fields

Storing the controller and the result as private fields in the Step class is fine until you want to test a second controller. I added a scenario for testing a Tenant controller:

Scenario: Visit the tenant pageWhen I visit the tenant pageThen I should see the Index view

I added a new TenantControllerSteps file and wrote the ‘I visit the tenant page’ step, compiled and ran the tests and got a failure. The reason for this is that the test picked up the ‘Then I should see the Index view’ from the ProductControllerSteps which is great for reuse, not so great that the method used the _result field from the ProductControllerSteps which is of course null at that point. For a demonstration of this, check out Darren’s video: SpecFlow Anti-Pattern: Using Private Members to Retain State Between Steps. The answer is to store state in ScenarioContext.Current.

The refactored code is now in two class files, steps specific to ProductController

[Binding]public class ProductControllerSteps {[Given(@"There are (.*) products")]public void GivenThereAreXProducts(int productCount) {ProductController controller = CreateProductController(productCount);ScenarioContext.Current.Set(controller);}[When(@"I visit the product page")]public void VisitTheProductPage() {var controller = ScenarioContext.Current.Get<ProductController>();ActionResult result = controller.Index();ScenarioContext.Current.Set(result);}[Then(@"the view should contain a list of products")]public void ThenTheViewShouldContainAListOfProducts() {var viewResult = (ViewResult) ScenarioContext.Current.Get<ActionResult>();viewResult.ViewData.Model.ShouldBeType(typeof (List<Product>));}[Then(@"the view should contain a list of (.*) products")]public void ThenTheViewShouldContainAListOfXProducts(int productCount) {var viewResult = (ViewResult) ScenarioContext.Current.Get<ActionResult>();var products = (List<Product>) viewResult.ViewData.Model;products.Count.ShouldEqual(productCount);}private static ProductController CreateProductController(int productCount) {var context = new FakeRavenContext();context.AddProducts(productCount);return new ProductController(new ProductRepository(context));}public ProductControllerSteps() {ProductController controller = CreateProductController(0);ScenarioContext.Current.Set(controller);}}

And steps that can be reused across different controllers

[Binding]public class ControllerSteps {[Then(@"I should see the (.*) view")]public void ThenIShouldSeeTheView(string viewName) {var result = ScenarioContext.Current.Get<ActionResult>();result.ShouldBeType(typeof (ViewResult));var viewResult = (ViewResult) ScenarioContext.Current.Get<ActionResult>();viewResult.ViewName.ShouldEqual(viewName);}[Then(@"the view title should be (.*)")]public void ThenTheViewTitleShouldBeProducts(string viewTitle) {var viewResult = (ViewResult) ScenarioContext.Current.Get<ActionResult>();viewResult.ViewData["Title"].ShouldEqual(viewTitle);}}

3 Use Tables to Specify Entities

In the code above, I am checking that the correct number of products are displayed, I am not testing they are the correct products though, this will become more important when I start adding and editing products. Darren has a library on Github to help with using tables in SpecFlow and there is a video on TekPub in which he explains it.

The refactored feature now looks like this:

Feature: View a list of productsIn order to see what products there areAs a userI want to view a list of productsScenario: Visit the product pageWhen I visit the product pageThen I should see the Index viewAnd the view title should be ProductsAnd the view should contain a list of productsScenario: Display 10 productsGiven There are these products|Id           |Active      |Name        |Reference    ||1            |true        |Product 1   |Ref-1        ||2            |true        |Product 2   |Ref-2        ||3            |true        |Product 3   |Ref-3        ||4            |true        |Product 4   |Ref-4        ||5            |true        |Product 5   |Ref-5        ||6            |false       |Product 6   |Ref-6        ||7            |false       |Product 7   |Ref-7        ||8            |false       |Product 8   |Ref-8        ||9            |false       |Product 9   |Ref-9        ||10           |false       |Product 10  |Ref-10       |When I visit the product pageThen the view should contain a list of these products|Id           |Active      |Name        |Reference    ||1            |true        |Product 1   |Ref-1        ||2            |true        |Product 2   |Ref-2        ||3            |true        |Product 3   |Ref-3        ||4            |true        |Product 4   |Ref-4        ||5            |true        |Product 5   |Ref-5        ||6            |false       |Product 6   |Ref-6        ||7            |false       |Product 7   |Ref-7        ||8            |false       |Product 8   |Ref-8        ||9            |false       |Product 9   |Ref-9        ||10           |false       |Product 10  |Ref-10       |Scenario: Display 0 productsGiven There are these products|Id           |Active      |Name        |Reference    |When I visit the product pageThen the view should contain a list of these products|Id           |Active      |Name        |Reference    |

And the two steps that have been amended are now:

[Given(@"There are these products")]public void GivenThereAreTheseProducts(Table table) {var products = table.CreateSet<Product>();var context = new FakeRavenContext {Products = new FakeDbSet<Product>(products)};var controller = new ProductController(new ProductRepository(context));ScenarioContext.Current.Set(controller);}[Then(@"the view should contain a list of these products")]public void ThenTheViewShouldContainAListOfTheseProducts(Table table) {var viewResult = (ViewResult) ScenarioContext.Current.Get<ActionResult>();var products = (List<Product>) viewResult.ViewData.Model;table.CompareToSet(products);}

I was expecting tables to be a pain but Darren’s Assist library really makes using tables easy in SpecFlow.

Previous
Previous

Learning Ruby (on Rails)

Next
Next

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