RSpec Stories On Rails
Using a story to drive your development tasks is a good idea, even when there is only 1 team member. Writing stories helps you stay focused on the requirements without getting side-tracked into something that isn’t working toward the end goal of your app. There’s always time for script.aculo.us later! Using RSpec stories also means you have tests to run against your code so you know that fixing something probably didn’t break something else.
First thing you want to do is get the RSpec on Rails plugin installed, and generate the RSpec code.
$ ./script/plugin install git://github.com/dchelimsky/rspec.git
$ ./script/plugin install git://github.com/dchelimsky/rspec-rails.git
$ ./script/generate rspec
Stories are broken down into 2.5 files:
- 1. The Story file - Where the plain text story resides
- 2. The Step file - Where the different steps in your story are defined.
- 2.5 The Story runner file - The ruby code that kicks everything off.
First off, the story file is a semi-plain text description of some stuff that happens in your application, and follows this basic format:
Story: <Description of Story>
As a <Whoever is doing the actions>
I want to <What you want to do>
So I can <Why you want to do it>Scenario: <Description of a Scenario that could happen>
Given <Something>
When <Something happens>
Then <What is the expected result>Scenario: <Description of a Scenario that could happen>
Given <Something>
When <Something happens>
Then <What is the expected result>
Not exactly conversational English, but no crazy ||’s &&’s or ==’s that would scare normal customers away. The top of the story is there to give you a general overview of what’s being tested. In each story, you’ll have different scenarios, mini-stories if you will, that cover a complete set of actions that can take place.
In each Scenario there are three basic statements in which you describe what state the application is in and the assumptions about the outside world, the actions that are taken, and the expected result. So, for a user regestering for the first time, you are assuming that the user has a username and password picked out ,and an email address. That user would then enter those into your regestration form, and then when they’re submitted, he’d be wisked away to some kind of confirmation page, and that user added to the database. This file (/stories/signup_for_accounts_story) may look like:
Scenario: User fills out form correctly
Given a username ‘brittany’
And a password ‘foobar’
And an email ‘foo@bar.com’
And there is no user with this usernameWhen the user creates an account with username, password and email
Then there should be a user named ‘brittany’
And there should be a profile for ‘brittany’
And should redirect to users profile page
Pretty straight forward, huh? You can have as many scenarios as you need to cover whatever part of your application you’re testing. At this point, everything is still pretty magical. How does RSpec know so much about your application that it can guess method names, form paths and so on? Well….it doesn’t. That’s where the step file comes in.
The step file is where you define what all of these arbitrary lines of text should actually do. Each line is defined in a block in your steps file, which normally lives in the stories/steps folder, and looks like this
steps_for(:<what you’re doing>) do
Given “<an assumption>” do
<do something having to do with that assumption>
endWhen “<an action>” do
<do that action>
endThen “<an expected result>” do
<see if that result is what…resulted>
endend
That’s the general layout of the steps file. You can have as many steps as you need, and they’re reused between scenarios. Your Given blocks will usually do something like mock a model, assign a value to a variable, or stub an outside service. These lay the basis for your assumptions for the state of being before your action is excuted. When blocks execute actions, such as posting to a web form. Finally, Then describes the expected state of your application after the action is executed. These are where you’ll usually find out where your application is broken. In pratice, this (/stories/steps/signup_for_accounts_steps.rb) looks like:
steps_for(:signup) do
Given “a username ‘$username’” do |username|
@username = username
endGiven “a password ‘$password’” do |password|
@password = password
endGiven “a password_confirmation ‘$pass_conf’” do |pass_conf|
@password_confirmation = pass_conf
endGiven “an email ‘$email’” do |email|
@email = email
endGiven “there is no user with this username” do
User.find_by_login(@username).should be_nil
endWhen “the user creates an account with username, password and email ” do
post “/users”, :user => { :login => @username,
:password => @password,
:password_confirmation => @password,
:email => @email }
endThen “there should be a user named ‘$username’” do |username|
User.find_by_login(username).should_not be_nil
endThen “there should be a profile for ‘$username’” do |username|
user = User.find_by_login(username)
user.should_not be_nil
Profile.find_by_user_id(user.id).should_not be_nil
endThen “And should redirect to users profile page” do
user = User.find_by_login(@username)
user.should_not be_nil
response.should redirect_to(user)
endend
This defines all the actions that we need to run our story. Kind of tedius, i know, but you can reuse blocks in other scenarios. If you notice, some blocks have a $variable, these are picked up by the RSpec story parcer and passed into the block, so it makes the blocks more flexable, and you can use them more often.
Finally, there is the half-file, the story runner, all it does is link everything you need togeather, and it (/stories/signup_for_account_story.rb) will look like this:
require File.dirname(__FILE__) + “/helper”
with_steps_for(:signup) do
run_local_story “signup_for_account”, :type => RailsStory
end
yeah, not much to it, eh? Just change the with_steps_for() symbol and the run_local_story argument and you’ll be on your way.
Now you can run your new story by typing in
$ ruby stories/signup_for_account_story.rb
and watch it make sure your app is doing what it’s supposed to do. It’ll notify you of any failures, and stop the test if it encounters them, so you can fix them right away.
There are many more, much better resources for this than mine, so if you want to check them out, here is a list of some of the better ones.
David Chelimsky: Integration Testing With Rspec’s Story Runner …
evang.eli.st User stories with RSpec’s Story Runner
RSpec plain text story runner on a fresh rails app
If you find anything in here that is dumb, crappy, or just plain wrong, drop a comment and i’ll fix it so it sucks less. Thanks.
Picture Credits: jay | http://conversationswithplasticdinosaurs.com/
Posted: June 27th, 2008 under Ruby on Rails.
Comments: none
Write a comment