Entries Tagged 'stories' ↓

Easy PlainText Stories in Ruby on Rails using WebRat

Building off of my last post on Stories, I’d like to show you some improvements I was able to make with the help of WebRat.

You can install WebRat using:

script/plugin install http://svn.eastmedia.net/public/plugins/webrat/

I got the inspiration from Ben Mabey at ‘ben.send :blog’ which illustrates an approach to stories that is unbelievably easy to pick up. He also provides source code to the example that he’s built so that you can play with it. Very cool!

I snagged that source myself and started playing with it, and added a lot of functionality of my own that I found useful. The real cool thing is that the ONLY application specific code I have is this:

# user_steps.rb
steps_for(:user) do
  
  Given("that $actor is not logged in") do |actor|
    get "/sessions/destroy"
    response.should be_redirect
    flash[:notice].should == "You have been logged out."

    #fails here even though in the next request it will pass
    #controller.should_not be_logged_in
  end
  
  Then("$actor $should be logged in") do |actor, should|
    controller.send(shouldify(should), be_logged_in)
  end
  
end

# navigation_steps.rb
def path_from_descriptive_name(page_name)
  case page_name
  when "home"
    ""
  when "signup"
    "users/new"
  when "login"
    "sessions/new"
  else
    "#{page_name}"
  end
end

With that, and the generic application-independent helper methods that also live in my steps directory, I can write the following story:

Story: Making posts

  As a visitor
  I want to be a member
  So that I can make posts

    Scenario: Glenn creates a new account
      Given that Glenn is not logged in
      And there are some number of users
      And no user with login 'Glenn' exists
      
      When he goes to the signup page
      And fills in Login with 'Glenn'
      And fills in Email with 'glenn@glennfu.com'
      And fills in Password with 'blah'
      And fills in Confirm Password with 'blah'
      And clicks the 'Sign up' button

      Then there should be 1 more user
      And a user with login 'Glenn' should exist


    Scenario: Glenn logs in
      GivenScenario Glenn creates a new account

      When Glenn goes to the login page
      And fills in Login with 'Glenn'
      And fills in Password with 'blah'

      Then he should be logged in
    
    
    Scenario: A member makes a post while logged in
      GivenScenario Glenn logs in
      And there are some number of posts

      When he goes to the posts page
      And clicks on 'New post'
      And fills in Title with 'Woo'
      And fills in body with 'Stuff here'
      And clicks the 'Create' button

      Then there should be 1 more post
      And a post with title 'Woo' should exist


    Scenario: A nice lady makes a post while not logged in
      Given that she is not logged in
      Then she should not be logged in

      Given there are some number of posts

      When she goes to the posts page
      And clicks on 'New post'

      Then she should see the login page


    Scenario: An angry visitor should be able to see posts
      Given that he is not logged in

      When he goes to the posts page

      Then he better not f'ing see the login page


    Scenario: A visitor should not be able to edit posts
      GivenScenario A member makes a post while logged in
      And that she is not logged in

      When she goes to the posts page
      And clicks on 'Edit'

      Then she should see the login page

Pretty cool, huh? I was even able to add some personality with the angry visitor Scenario. Notice that I don’t have any steps written about Posts or my PostsController. I don’t need it because I have generic model steps that parse the model from the text in my PlainText instructions. It has all the basics that you need. You interact with forms and links on the top level by name, and then check the results either by text on the page or by finding models.

If you’d like to play with it yourself, I’ve zipped up my stories folder for you to play with.

Download it here!

It also has the old user_story.rb from the previous example. If you have any suggestions for improvements to this or ideas for something else I should do with it, please leave a comment!

My only quirk with this is that if you check the first step in the steps_for(:user) above you’ll see I have a line commented out. It checks for controller.should_not be_logged_in and if I uncomment it, the check fails even though the two above it pass. As you can see in the PlainText, on the very next line I call the very same check and it passes. If you could help me figure out my mistake here I’d really appreciate you leaving a comment with your thoughts!

I hope you find this useful, and if so, be sure to go toss a thank you over to Ben for the inspiration.

Reuse Given Scenarios with GivenScenario in RSpec StoryRunner

Update: I’ve made some improvements upon this using WebRat that you can read all about here.

I’m having a great time with RSpec‘s new StoryRunner, and wanted to share a bit of what you can do with it. I learn very well from examples and I know many others do as well so I hope that this provides as a useful resource for getting your ideas flowing on how to use StoryRunner for your own projects.

To start, I set up a simple project using the following

# Create the project
rails blog
cd blog

# Install Restful Authentication
script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/
script/generate authenticated user sessions

# Create a Post model
script/generate scaffold post title:string body:text published:boolean

Now I have the essentials for a simple app to play with. Of course you’ll also want to install RSpec.

Now to try out StoryRunner! I’ll write a story to cover the login system. No need to write Spec’s for it, it’s already well tested. However in writing this story we get a reusable Scenario that will be nice to have later, as well as give us a nice starting point for stories.

require File.join(File.dirname(__FILE__), "helper")

Story "User Stuff", %{
  As a writer
  I want to be a member
  So that I can make posts
}, :type => RailsStory do
  
  Scenario "I log in as a new user" do

    Given("that I am not logged in") do
      delete "/sessions/destroy"
      controller.should_not be_logged_in
      
      @user_count = User.count
    end

    When("I create a new user", "Glenn") do |name|
      @user = User.create(:login => name, 
            :email => "#{name}@glennfu.com", :password => name, 
            :password_confirmation => name)
    end

    Then("there should be 1 more user stored") do
      User.count.should == @user_count + 1
    end

    When("I login as", "Glenn") do |name|
     post "/sessions/create", :login => name, :password => name
    end
    
    Then("I should be logged in") do
      controller.should be_logged_in
      response.should be_redirect
    end
  end
end

This should be pretty easy to follow. I start by logging out, then creating a user and logging in with it. This is runnable with ruby stories/user_story.rb

Now that this is already written, we can easily add similar stories. Notice that we don’t have to re-define our steps here:

Scenario "I log in with the wrong user/password" do

  Given "that I am not logged in"
  When "I create a new user", "Glenn"
  Then "there should be 1 more user stored"
  When "I login as", "JoeBob"
  
  Then "I should not be logged in" do
    controller.should_not be_logged_in
    response.should_not be_redirect
  end
end

Now I’ll write a story for our Posts. Notice the usage of “GivenScenario”, very cool!

Scenario "Create a new post while logged in" do

  GivenScenario "I log in as a new user"

  Then "I should be able to make a post" do
    post "/posts/create", :post => {:title => "the title", 
            :body => "The body!"}
    
    @post = Post.find_by_title("the title")
    
    flash[:notice].should == "Post was successfully created."
    @post.should_not be_nil
    response.should be_redirect
  end
end

What if I want to also test posts failing? I’ll modify the previous step to be a bit more robust (and also more useful).

Then "I $should be able to make a post", "should" do |should|
  post "/posts/create", :post => {:title => "my title", 
            :body => "The body!"}
  
  @post = Post.find_by_title("my title")
  
  if should == "should"
    flash[:notice].should == "Post was successfully created."
    @post.should_not be_nil
  else
    flash[:notice].should be_nil
    @post.should be_nil
  end
  response.should be_redirect
end

With this change, I can easily add a new Scenario to test when making posts should fail:

Scenario "Create a new post while not logged in" do
  Given "that I am not logged in" 
  Then "I $should be able to make a post", "should not"
end

I hope you found this useful!