test/unit story runner
on November 14, 2007 @ 01:53 AM

As you may know rspec has integrated RBehave into itself. In the rspec world it’s more recently been called Story Runner.

Some background links:

Story Runner itself hasn’t officially been released and while it has been becoming more polished myself and a colleague (Drew Colthorp) wanted higher level acceptance tests in my ruby projects (including rails).

Story Runner also has two implementations (or at least APIs). The latest is the plain text Story Runner. This requires that for every story you have at least two files. One that is plain text for the customer to write and one that is a “matcher” file developers write. Information on this can be found here

The older version of Story Runner uses the original RBehave style syntax. Lots of blocks and lots of argument passing. There is also a bug in this implementation regarding sharing blocks given to story parts. But enough about rspec, this post is on a lighter weight and simpler Story Runner.

So here’s to a working and testing implementation of a Story Runner in test/unit.

test/unit Story Runner

To start, an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class EventCreationTest < ActionController::IntegrationTest
  fixtures :all

  Story "As a user I should be able to create an event so that others can RSVP and attend."
  
  Scenario "Creating an event unsuccessfully with insufficient input" do
    Given :a_user_at_the_create_an_event_page
    When :they_submit_the_create_an_event_form_with_insufficient_input
    Then :they_will_see_an_error_explanation
    And :they_will_see_the_create_an_event_page
  end 

   #
   # HELPERS
   #
   def a_user_at_the_create_an_event_page
       # ....
   end

   def submit_the_create_an_event_form_with_insufficient_input
       # ...
   end

   def see_an_error_explanation
       # ...
   end

   def see_the_create_an_event_page
      # ...
   end
end

Some important things to note on the structure:

  • the Story takes a description of the story. It does NOT take a block.
  • multiple Story’s can be defined in a file although I prefer organizing them in separate files
  • all Scenario’s which follow a Story will belong to that Story.
  • the Scenario takes a description of the particular scenario or acceptance test for a story. It does take a block.
  • inside of a Scenario you describe the story parts. These are Given, When, Then, and And.

Some important things to note on the story part descriptions:

  • Each story part can take a string or symbol description
  • helper method calls are generated off from the story part description

Helper method calls are generated by taking the full story part description and then removing the leading word until a helper method is found. If there are no helper methods found then an exception is raised.

Some important things to note on the goals of this implementation:

  • in theory customers write acceptance tests
  • in practice developers translate customer desires into acceptance tests
  • ideally the acceptance tests should be high level enough that a customer can read/write them
  • since developers will most likely write these acceptance tests this implementation should allow for developer shortcuts
  • this should just work on a test/unit project, projects updating to this shouldn’t have to convert or make changes up front to existing code

script/plugin install

script/plugin install \
  http://continuous.rubyforge.org/svn/tags/test_unit_story_runner-0.1.0

Final Thoughts

Before I forget you can do argument passing as well on story parts:

1
2
3
4
5
Scenario "a user successfully making a guess in the game" do
  Given :a_user_at_the_game
  When :they_make_a_guess_of, 20_000
  Then :they_will_win_a_car
end

This feature is there, but I would advocate only using it when it helps the readability of your tests.

This is a stable (tested) experiment. It’s a thin wrapper around the test/unit framework and it works with any Test::Unit::TestCase. The price to pay for higher level acceptance tests is now next to nothing so give it a shot.

This is the creation of a trip to Madison, WI where Drew and I had a chance to discuss and vent about the state of acceptance tests in Rails projects specifically. A few days later we had this implementation, and about a month later you’ve got this post…

Suggestions and feedbacks are warmly welcomed.

Happy rubying!

0 comments | Filed Under: | read on

rspec_on_rails render_and_receive_matcher
on November 14, 2007 @ 12:10 AM

Earlier today my pair and I saw a repetitious pattern in our view specs when we were using helpers. It looked similar to the following:

1
2
3
4
5
 it "displays foo" do
    template.should_receive(:bar).with(@foo).and_return(%|<p id='bar'></p>|)
    render_it
    response.should have_tag("p#bar")
 end

The repetition was in the three lines:

  • setup a simple should receive and return
  • make the call to render
  • verify the response had the returned element

Tonight I spent some time spiking a way to cure this repetition. I’m a bigger fan of verbosity in specs over DRYness, but I think my exploration gives us something good. The above example now looks like:

1
2
3
4
5
 it "displays foo" do
    during_render do
      template.should receive_and_render(:bar).with(@foo)
    end
 end

The receive_and_render method sets up a should_receive expectation on the template. For completeness there is also a stub_and_render method. As you might guess this method sets up a stub! on the template.

This doesn’t just work on the template object. It works on any object, but you should probably only use it on objects that show up in the view.

script/plugin install

script/plugin install \    
  http://continuous.rubyforge.org/svn/trunk/rspec/matchers/rspec_on_rails_render_and_receive

Final Thoughts

This in an experiment. As I am learning more about the spirit of rspec and it’s organization there are a few things I want to change with this. Right now this is implemented as a matcher which doesn’t feel quite right, but time will tell. So in the meantime… Happy rubying!

3 comments | Filed Under: | read on