Paul Barry

Story Driven Development with Rails - Part I: Up and Running

September 16, 2008

Earlier this year at the Gotham Ruby Conference, Brian Helmkamp gave a talk on Story Driven Development. It’s a fantastic talk, I suggest that everyone view it. In it he explains how to use RSpec Users Stories combined with Webrat for full-stack executable scenario testing. He also covers what value executable scenarios provide and where executable scenarios fit into your overall web application testing strategy. This article provides you with a more detailed how-to of the steps required to get up and running with Story Driven Development with Rails.

In this article I don’t go to much into the details of how story runner works, so I would say another prerequisite for this article is Geoffrey Grosenbach’s RSpec User Stories available on PeepCode.

So let’s say we are building a rails version of this blog. First steps are the basic rails setup:

$ rails blog
$ cd blog
$ script/plugin install git:// -r 1.1.4
$ script/plugin install git:// -r 1.1.4
$ script/generate rspec      
$ script/plugin install git:// 

That gives us an rspecified Rails app, which has a stories directory, all ready for us to put our stories in. Fixture Replacement is an implementation of the factory pattern for creating test data, which is an alternative to using fixtures data in yaml files. So let’s create the first story in stories/view_articles_story.txt:

Story: View Articles

As a reader of the blog
I want to view articles
So that I can read what you have to say

  Scenario: display most recently published articles
    Given 5 articles have been published
    When I visit the homepage
    Then I should see the articles

A couple of things to point out. First, the naming convention I’m going with is to put the plain text story in <story_name>_story.txt and the story runner file in <story_name>_story.rb. Next I’d like to talk a little bit about this story. I choose to write this story first because it is the most important feature in the application. Generally I like to try to follow this rule. When writing a stories, always write a story for the next most important feature.

So in order to use this story, we need a runner. So let’s put this into stories/view_articles_story.rb:

require File.dirname(__FILE__) + "/helper"

run_story :view_articles

This is about concise as it gets for this file. The magic happens in stories/helper.rb. Add this to the bottom of that file:

def run_story(story_name, options={})
  with_steps_for(story_name) do
      File.join(File.dirname(__FILE__), "#{story_name}_story.txt"), 
      { :type => RailsStory }.merge(options)

What this does is allow you to call run_story to run a story. It assumes by default that you will want to use the steps that are defined in the same file with the same name as the story. We’ll probably enhance run_story in the future to do more stuff, but this is all we need for now. Now you can run either stories/all.rb or stories/view_articles_story.rb and you should get:

Running 1 scenarios

Story: View Articles

  As a reader of the blog
  I want to view articles
  So that I can read what you have to say

  Scenario: Homepage

    Given 5 articles have been published (PENDING)

    When I visit the homepage (PENDING)

    Then I should see the articles (PENDING)

1 scenarios: 0 succeeded, 0 failed, 1 pending

Pending Steps:
1) View Articles (Homepage): 5 articles have been published
2) View Articles (Homepage): I visit the homepage
3) View Articles (Homepage): I should see all 5 articles

So we now have yellow (a.k.a pending) scenarios, so step 1 is done. The next step is to write step matchers, which should make our scenario fail. So we add this to stories/view_articles_story.rb:

steps_for(:view_articles) do
  Given "$count articles have been published" do |count|
    @count = count.to_i
    @count.times {|n| create_article(:title => "Article ##{n}") }
  When "I visit $path" do |path|
    get path
  Then "I should see the articles" do
    @count.times do |n|
      response.should have_tag("h2", "Article ##{n}")

Make sure to add that before the run_story :view_articles line. Now you should have red (a.k.a failing) scenarios, that look something like this:

    1) View Articles (Homepage) FAILED
    NoMethodError: undefined method `create_article' for   

Make sure they are failing for the right reason. For example, when I first wrote this, I forgot that all the of variables are captured as strings and I left off the call to .to_i on count. I get failing specs, but not because I haven’t implemented the code, because my steps are wrong. Remember, tests can have bugs too.

Let’s take a look at each of these steps. Inside the given step, we are calling create_article. Now as you noticed from the failure message, there is no create_article method yet. Fixture Replacement will take care of defining that for us once we have the articles model created and example_data.rb created. In the when step, we are capturing the path the user is trying to access, and then using the same rspec controller helper method we would use if this were a controller spec. get /some_path goes through the routing to call our controller with an HTTP get. Finally, in the then step, we just check that the HTML in our response has an H2 element with the title of the article in it, for each of the articles we created in the given step.

So now on to step 3, which is to write the code to make it go green. To make this easy, we’ll use the rspec scaffold generator:

$ script/generate rspec_scaffold Article title:string body:text
$ rake db:migrate
$ rake db:test:prepare

So if we run stories/view_articles_story.rb again, we get the same error. That’s because we still haven’t told Fixture Replacement about our article model. So create the file db/example_data.rb and put this in it:

module FixtureReplacement
  attributes_for :article do |a|
    a.title = "First Post!"
    a.body = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit..."

This defines the minimum set of data required to create a valid article. You can override any value by simply passing it in the hash, as we are doing in the given step. Fixture Replacement gives you two methods for each model, which are in this case, create_article and new_article, which do pretty much what you would expect. You can read more about Fixture Replacements in the README.

Last but not least, we have to include FixtureReplacement in our RSpec Story Runner, which is as simple as adding the line include FixtureReplacement somewhere near the top of stories/helper.rb, after all the other require statements.

So now if we run our story, we get something like:

1) View Articles (Homepage) FAILED
    Expected at least 1 element matching "h2", found 0.
    <false> is not true.

Which is still failing, but it’s progress. Obviously the problem now is that our view isn’t outputting H2 elements for the article titles. That’s an easy fix which I’ll leave as an exercise to the reader (hint: modify the default route, edit the articles index ERB template).

One might argue that checking for H2s is checking the implementation details, which could be fragile if our designer decides to use DIVs instead, for example. That decision is up to you, personally I like to produce the correct semantic markup, have my tests validate that and apply CSS from there. If you’d rather your tests be less coupled to the DOM structure, you could just change your expectation in the then step to response.should have_text("Article ##{n}").

Hopefully that gets you up and running with Fixture Replacement and RSpec User Stories. Tune in for Part II of the series, where we cover more complex interactions with Webrat.