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.
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:
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.