David Chelimsky

random thoughtlessness

Advanced Rails Recipes

I’m pleased to announce that my contribution to Mike Clark’s new Advanced Rails Recipes book has been accepted and released. The book is available right now as a Beta PDF and includes dozens of delicious and nutritious recipes for enhancing your Rails applications and the process of developing them.

This is my first formal publication in the software world [1], and I couldn’t be more pleased than to have it be a Pragmatic Bookshelf [2] publication.

My recipe is entitled Describing Behaviour from the Outside-In With RSpec and it demonstrates the BDD approach to Rails apps starting with the view and working your way down to the controllers, models and database.

There is currently one other BDD recipe: Getting Started with BDD, which uses shoulda. There are also recipes for cooking up mocks, code coverage and html validity. And that’s just the testing related recipes.

There are also dozens of recipes dealing with UI, search, email, console, REST, db enhancements and even more general design improvements.

Like the first Rails Recipes book, this one is a must-have for any serious Rails developer who wants to take it up a notch in creating great web applications with Rails.


[1] My first publication of any kind was back around 1980 when I was a young professional magician. The book is called, simply, Coin Magic, and is a must have for any serious coin magician who wants to take it up a notch (see a trend here?) in presenting awesome feats of magic with ordinary coins. Back then I went by my first and middle name, David Arthur.

[2] The Pragmatic Programmers are also publishing my upcoming book with co-author Aslak Hellesøy, tentatively entitled Behaviour Driven Development in Ruby with RSpec.

RSpec 1.1

The RSpec Development Team is pleased as glug (that’s kind of like punch, but more festive) to announce RSpec-1.1.0.

Thanks to all who have contributed patches over the last few months. Big thanks to Dan North and Brian Takita for their important work on this release. Dan contributed his rbehave framework which is now the Story Runner. Brian patiently did a TON of refactoring around interoperability with Test::Unit, and the result is a much cleaner RSpec core, and a clean adapter model that gets loaded when Test::Unit is on the path.

RSpec 1.1 brings four significant changes for RSpec users:

  • The RSpec Story Runner

  • Nested Example Groups

  • Support for Rails 2.0.1

  • Test::Unit interoperability

Story Runner

The RSpec Story Runner is Dan North’s rbehave framework merged into RSpec. The Story Runner is a framework for expressing high level requirements in the form of executable User Stories with Scenarios that represent Customer Acceptance Tests.

RSpec 1.1 also ships with a Ruby on Rails extension called RailsStory, which lets you write executable user stories for your rails apps as well.

Nested Example Groups

Now you can nest groups to organize things a bit better:

<code>
describe RubyDeveloper do

before(:each) do
  @ruby_developer = RubyDeveloper.new
end

describe "using RSpec 1.1.0" do

  before(:each) do
    @ruby_developer.use_rspec('1.1.0')
  end

  it "should be able to nest example groups" do
    @ruby_developer.should be_able_to_nest_example_groups
  end

end

describe "using RSpec 1.0.1" do

  before(:each) do
    @ruby_developer.use_rspec('1.0.8')
  end

  it "should not be able to nest example groups" do
    @ruby_developer.should_not be_able_to_nest_example_groups
  end

end

end
</code>

Running this outputs:

RubyDeveloper using RSpec 1.1.0
- should be able to nest example groups

RubyDeveloper using RSpec 1.0.8
- should not be able to nest example groups

== Support for Rails 2.0.1

gem install rails
rails myapp
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/REL_1_1_0/rspec
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/REL_1_1_0/rspec_on_rails
script/generate rspec

Test::Unit Interoperability

Contrary to popular belief, Spec::Rails, RSpec’s Ruby on Rails plugin, has been a Test::Unit wrapper since the the 0.7 release in November of 2006. RSpec 1.1 ups the ante though, offering a smooth transition from Test::Unit to RSpec with or without Rails:

  1. Start with a TestCase:

    require ‘test/unit’

    class TransitionTest < Test::Unit::TestCase def test_should_be_smooth transition = Transition.new( :from => “Test::Unit::TestCase”, :to => “Spec::ExampleGroup” ) assert_equal “really smooth”, transition.in_practice end end

  2. Require ‘spec’

    require ‘test/unit’ require ‘spec’

    class TransitionTest < Test::Unit::TestCase def test_should_be_smooth transition = Transition.new( :from => “Test::Unit::TestCase”, :to => “Spec::ExampleGroup” ) assert_equal “really smooth”, transition.in_practice end end

  3. Convert TestCase to ExampleGroup

    require ‘test/unit’ require ‘spec’

    describe “transitioning from TestCase to ExampleGroup” do def test_should_be_smooth transition = Transition.new( :from => “Test::Unit::TestCase”, :to => “Spec::ExampleGroup” ) assert_equal “really smooth”, transition.in_practice end end

  4. Convert test methods to examples

    require ‘test/unit’ require ‘spec’

    describe “transitioning from TestCase to ExampleGroup” do it “should be smooth” do transition = Transition.new( :from => “Test::Unit::TestCase”, :to => “Spec::ExampleGroup” ) assert_equal “really smooth”, transition.in_practice end end

  5. Convert assertions to expectations

    require ‘test/unit’ require ‘spec’

    describe “transitioning from TestCase to ExampleGroup” do it “should be smooth” do transition = Transition.new( :from => “Test::Unit::TestCase”, :to => “Spec::ExampleGroup”) transition.in_practice.should == “really smooth” end end

  6. Un-require test/unit

    require ‘spec’

    describe “transitioning from TestCase to ExampleGroup” do it “should be smooth” do transition = Transition.new( :from => “Test::Unit::TestCase”, :to => “Spec::ExampleGroup” ) transition.in_practice.should == “really smooth” end end

At every one of these steps after step 2, you can run the file with the ruby command and you’ll be getting RSpec’s developer friendly output. This means that you can transition things as gradually as you like: no wholesale changes.

That’s the story. Thanks again to all who contributed and to all who continue do so.

RubyConf 2007 Talk

The RSpec/BDD talk that Dave Astels and I presented at RubyConf 2007 has been posted for your viewing pleasure.

There are also a bunch of other RubyConf talks posted on the same site.

One correction: In our talk, I said that the role/feature/reason story format came from Mike Cohn’s book User Stories Applied. The real source was a project team at Connextra that Tim MacKinnon was a part of. Tim later joined Thoughtworks, where Dan North learned about it from Tim.

I did, in fact, learn about it from Mike Cohn, but it was at a talk he did at an Agile Conference a couple of summers back. I had also read his book, and somewhere in my head merged the talk and the book.

Nested Example Groups

Since rspec first appeared on the scene, users have been asking for nested example groups. Well it has finally arrived. RSpec 1.1.0 will ship with support for nesting, so you’ll be able to do things like this:

<code>
describe RSpec do
before(:each) do
  @rspec = RSpec.new
end

describe "at release 1.0.8" do
  before(:each) do
    @rspec.version = "1.0.8"
  end

  it "should not support nested example groups" do
    @rspec.should_not support_nested_example_groups
  end
end

describe "at release 1.1.0" do
  before(:each) do
    @rspec.version = "1.1.0"
  end

  it "should support nested example groups" do
    @rspec.should support_nested_example_groups
  end
end
end
</code>

This will output:

RSpec at release 1.0.8
- should not support nested example groups

RSpec at release 1.1.0
- should support nested example groups

If you’re using trunk, you can do this now with revision 3009 or later.

Happy nesting!

before_action/after_action

A while back there was either a feature request in the rspec tracker, or a suggestion on one of the rspec mailing lists, for a feature to DRY up controller specs.

The idea was that this pattern feels a bit clunky:

<code>describe PersonController, "handling failed POST to create" do
def do_post
  post :create, invalid_arguments
end

it "should redisplay the create form" do
  do_post
  response.should render_template("people/new")
end

it "should try to create a Person" do
  Person.should_receive(:create).with(invalid_arguments).and_return(false)
  do_post
end
end
</code>

And it would be nice to have something that was more expressive using tags like this:

<code>describe PersonController, "handling failed POST to create" do
def do_post
  post :create, invalid_arguments
end

it "should redisplay the create form", :after => :do_post do
  response.should render_template("people/new")
end

it "should try to create a Person", :before => :do_post do
  Person.should_receive(:create).with(invalid_arguments).and_return(false)
end
end
</code>

I didn’t add this to rspec_on_rails because I personally find it harder to read. It also doesn’t support situations where you want to stub something before the action and then set a state-based expecation after the action.

But the problem is still present, and it would be nice to have something a bit less clunky.

Well – here’s what I’ve been experimenting with. This is NOT part of RSpec, and I may not want to include it in RSpec because I think it’s somewhat particular to my personal style (as opposed to a style that I think is “right” for BDD), but it’s easy enough to add to your own projects.

Here’s what the specs look like:

<code>describe PersonController, "handling failed POST to create" do
def do_post
  post :create, invalid_arguments
end

it "should redisplay the create form" do
  after_post do
    response.should render_template("people/new")
  end
end

it "should try to create a Person" do
  during_post do
    Person.should_receive(:create).with(invalid_arguments).and_return(false)
  end
end
end
</code>

I really like this even though it actually turns out to be a bit more verbose. I think it speaks very clearly about what is going on – especially “during_post”, which describes very well when the Person.should_receive the :create message.

Here’s the code in spec_helper.rb that supports this pattern:

<code>[:get, :post, :put, :delete, :render].each do |action|
eval %Q{
  def before_#{action}
    yield
    do_#{action}
  end
  alias during_#{action} before_#{action}
  def after_#{action}
    do_#{action}
    yield
  end
}
end
</code>

This supports controller and view specs (hence including :render).

Please try it out and let me know what you think.

Lessons in Spec’ing

Lesson: system calls in specs do stuff on your system. Be careful.

I was trying different things to understand why a spec was failing and, at one point, put a pending statement in a spec that generates a file and then deletes it. The statement that deletes the file is in an after(:each) block (which is guaranteed to run) and looks like this:

<code>system(%Q|rm "#{dir}/#{filename}"|)</code>

Imagine my horror when I saw this in the output:

rm: /: is a directory

Plain Text Stories: Part III

Here’s the latest update to Plain Text Stories. Effective r2789 in RSpec’s trunk.

Step 1: Write a Story

<code>Story: simple addition

As an accountant
I want to add numbers
So that I can count beans

Scenario: add one plus one
  Given an addend of 1
  And an addend of 1

  When the addends are added

  Then the sum should be 2
  And the corks should be popped

Scenario: add two plus five
  Given an addend of 2
  And an addend of 5

  When the addends are added

  Then the sum should be 7
</code>

Step 2: Create Steps

<code># This creates steps for :addition
steps_for(:addition) do
Given("an addend of $addend") do |addend|
  @adder ||= Adder.new
  @adder << addend.to_i
end
end

# This appends to them
steps_for(:addition) do
When("the addends are added")  { @sum = @adder.sum }
Then("the sum should be $sum") { |sum| @sum.should == sum.to_i }
end
</code>

Step 3: Let her open the box … no, that’s not it …

Step 3: Run the Story with the steps you want (adding any that are only for this story as you go).

<code>with_steps_for :addition do
Then("the corks should be popped") {}
run 'path/to/story/file'
end
</code>

Working with Rails?

<code>with_steps_for :navigation do
run 'path/to/story/file', :type => RailsStory
end
</code>

What about multiple groups of steps?

<code>
with_steps_for :login, :navigation, :form_submissions do
run 'path/to/story/file'
end
</code>

Coming soon to a computer near you … (as soon as you can “seven up”)

Plain Text Stories on Rails

Since my last post on plain text stories, there have already been a few improvements, not the least of which is that it will now work with Rails. Again, this is trunk (rev 2769+) only and experimental.

Here’s a working example from an app that I’m working on:

stories/login

Story: registered user logs in
As a registered user
I want to have to log in
So that only other registered users can see my data

Scenario: user logs in and sees welcome page
  Given a user registered with login: foo and password: test
  When user logs in with login: foo and password: test
  Then user should see the welcome page

Scenario: user logs in with wrong password
  Given a user registered with login: foo and password: test
  When user logs in with login: foo and password: wrong
  Then user should see the login form
  And page should include text: There was an error logging in.

Scenario: user logs in with wrong login name
  Given a user registered with login: foo and password: test
  When user logs in with login: wrong and password: test
  Then user should see the login form
  And page should include text: There was an error logging in.

[Update: modified to use runner.steps instead of runner.step_matchers]

stories/login.rb

<code>require File.join(File.dirname(__FILE__), *%w[helper])

run_story :type => RailsStory do |runner|
runner.steps << LoginSteps.new
runner.steps << NavigationSteps.new
runner.load File.expand_path(__FILE__).gsub(".rb","")
end
</code>

Here’s what’s new in this example:

  • run_story is added to the main object so you don’t have to remember that silly path to the PlainTextStoryRunner which will undoutedbly change!

  • run_story accepts arguments, including an options hash, which it will pass to the constructor of the PlainTextStoryRunner (in this case, :type => RailsStory)

  • run_story yields the runner, which now supports a load method which you use to tell it where to find the plain text story file.

  • run_story … runs the story

Keep your eyes peeled for more updates in the coming days.