let it be @-less
September 15th, 2009
If you use RSpec and you’re disciplined about the red/green/refactor of Test Driven Development, you probably find yourself doing this from time to time. We start off with a single example:
describe BowlingGame do it "scores all gutters with 0" do game = BowlingGame.new 20.times { game.roll(0) } game.score.should == 0 end end
Then add second example:
describe BowlingGame do it "scores all gutters with 0" do game = BowlingGame.new 20.times { game.roll(0) } game.score.should == 0 end it "scores all 1's with 20" do game = BowlingGame.new 20.times { game.roll(1) } game.score.should == 20 end end
Once we get the second example passing, we remove duplication in the examples, typically like this:
describe BowlingGame do before(:each) do @game = BowlingGame.new end it "scores all gutters with 0" do 20.times { @game.roll(0) } @game.score.should == 0 end it "scores all 1's with 20" do 20.times { @game.roll(1) } @game.score.should == 20 end end
This last step involves copying the first line of each example to a before(:each) block, and then converting the references to game to an instance variable using an @ symbol. This is tedious and error prone, but we accept that in the interest of keeping things clean.
rspec-1.2.9.rc1 and rspec-rails-1.2.9.rc1 have been released
September 15th, 2009
release candidates
We’re using the new rubygems prerelease feature to do proper release candidates. This feature was introduced to rubygems a couple of versions back, but I’d recommend updating to rubygems-1.3.5 before installing the rspec prerelease gems.
For those unfamiliar with this new rubygems feature, you have to add the --prerelease flag in order to see and install these gems:
$ gem search --remote --prerelease rspec
*** REMOTE GEMS ***
rspec (1.2.9.rc1)
rspec-rails (1.2.9.rc1)
$ [sudo] gem install --prerelease rspec
$ [sudo] gem install --prerelease rspec-rails
This way only those who choose to install the prerelease gems will get them, while those who exclude the –prerelease flag will get the previous final release (rspec-1.2.8, rspec-rails-1.2.7.1).
Once you install the prerelease gems, Rubygems will treat 1.2.9.rc1 as a higher version than 1.2.8, but a lower version than 1.2.9. That way when we do the final release you’ll be able to just install 1.2.9 and it will take its rightful place ahead of 1.2.9.rc1 without you having to uninstall rc1.
I invite you to install these prerelease gems and report any issues you run into to http://rspec.lighthouseapp.com/rspec. Advanced thanks to those who help the rest by breaking these in.
changes
Here are the changelogs for both rspec and rspec-rails. I’ll post separately about some of the new features in more detail.
rspec-1.2.9-rc1
enhancements
- manage backtrace-ignore patterns with Spec::Runner.configure (Martin Emde). Closes #870.
- friendly mock argument expectation failure message (Tim Harper). Closes #868.
- added double() as alias for stub() and mock()
- failure messages for doubles, mocks and stubs use the right name
- add let() method to assign memoized attributes (suggestion from Stuart Halloway). Closes #857.
- add its method so you can say: describe Array do its(:length) { should == 0 } (Stephen Touset). Closes #833
- spec command automatically uses spec/spec.opts if it is present (suggestion from Yehuda Katz)
- rspec now adds PROJECT_ROOT/lib and PROJECT_ROOT/spec to the load path
- determines PROJECT_ROOT by recursing up until it finds a directory that has a ./spec directory (thanks to Scott Taylor)
- supports require ’spec_helper’
- supports running specs from the PROJECT_ROOT or any directory below it
- closes #733
not really a bug fix or enhancement
- temporarily moved heckle feature to features-pending (waiting to see what happens with http://rubyforge.org/tracker/index.php?func=detail&aid=26786&group_id=1513&atid=5921)
rspec-rails-1.2.9-rc1
enhancements
- added route_to and be_routable matchers (Randy Harmon). Closes #843.
- Provide better failure message for render_template when redirected (Josh Nichols). Closes #885.
- generated specs require ’spec_helper’
bug fixes
- pass the correct args to super in controller#render depending on the rails version (Lucas Carlson). Closes #865.
- use Rack::Utils.parse_query to convert query strings to hashes. Closes #872.
- errors correctly bubble up when a controller spec in isolation mode requests a non-existent action/template
- no error if either action or template exist
- error if neither exist
- Closes #888.
removals
- spec_server has been removed in favor of spork.
- You can still use the –drb flag, but you’ve got to install the spork gem.
- Windows users who cannot use the spork gem can install the spec_server from http://github.com/dchelimsky/spec_server
- http://rubyforge.org/projects/rspec
- http://github.com/dchelimsky/rspec
- http://github.com/dchelimsky/rspec-rails
- http://wiki.github.com/dchelimsky/rspec
- rspec-users@rubyforge.org
- rspec-devel@rubyforge.org
Windy City Rails
July 18th, 2009
I’m presenting at Windy City Rails in September. This is just the second year of this exciting midwest Rails conference, but the schedule looks most impressive. Talks range from How To Test Absolutely Anything to Better Ruby through Functional Programming to a Rails 3 Update from Yehuda Katz, Rails core’s newest member and the man who is bringing the best of Merb to Rails 3.
Last year I did a presentation about BDD and, with such a wide subject, ran out of time before I got through most of it. This year, the organizers of the conference have given me the opportunity to talk for three full hours! Count ‘em. Three!
It’s actually not just me talking (whew!). It’s a tutorial entitled Behaviour Driven Rails with RSpec and Cucumber. I’m planning an intensive skills development workshop, taking attendees through the development of a feature in a Rails app from planning to writing Cucumber scenarios to driving out code with RSpec. This is going to be about as close as you’ll get to a BDD immersion in three hours, so I hope to see you there whether you’re just learning about BDD now or you’ve already been doing it for a while.
See you in September!
The RSpec Book: Beta 8.0
July 18th, 2009
The RSpec Book Beta 8.0 was just released. This release includes a number of fixed errata and one new chapter. Yes, only one chapter, but its a doozy:
Managing Complexity in Step Definitions
This chapter introduces a random generator to the Codebreaker game. This brings up several issues that can add complexity to Cucumber scenarios, RSpec code examples, and the code we’re driving out with the aid of these tools and the BDD process. We address these issues and offer strategies to manage the complexity they introduce.
RSpec works with test/unit
February 2nd, 2009
[Updated to Sept 5, 2009]
Did you know that rspec is interoperable with test/unit?
spec/rails (formerly rspec_on_rails) has always run on test/unit and rspec (core) has had t/u interop capability for over a year now.
Take, for example, this test in addition_test.rb:
require 'test/unit' class TestAddition < Test::Unit::TestCase def test_add_1_and_2 assert_equal 3, 1 + 2 end end
$ ruby addition_test
Loaded suite /Users/david/projects/ruby/tmp/tur/addition_test Started . Finished in 0.000289 seconds.1 tests, 1 assertions, 0 failures, 0 errors
Now, simply require ’spec/test/unit’:
require 'rubygems' require 'spec/test/unit' class TestAddition < Test::Unit::TestCase def test_add_1_and_2 assert_equal 3, 1 + 2 end end
Run it with the spec command that is installed when you install rspec:
$ spec addition_test
And tada!
.Finished in 0.001451 seconds
1 example, 0 failures
RSpec is running your tests!
A case against a case against mocking and stubbing
December 11th, 2008
In his blog entitled A case against Mocking and Stubbing, Brian Cardarella says
since I’ve been TATFT with TDD and some BDD I’ve come to believe that mocking/stubbing is a horrible idea and it can hurt the development process
Please take a minute to let that soak in. “a horrible idea” and “can hurt the development process”. In fact, please go read the post before you read on. I’d rather you read his words before you read my interpretation of them.
Back? Cool. Scary stuff, huh?
But never fear, because it’s not about you (unless you are Brian). What he is really saying is this:
since I’ve been TATFT with my own personal approach to testing Rails applications, which is a little bit different from what the TDD/BDD guys are doing and is largely based on Rails conventions which encourage you to couple layers together in your tests, I’ve come to believe that mocking and stubbing, two concepts that assist and encourage testing in isolation, which is the opposite of the kind of testing I like to do, is a horrible idea for me and can hurt my own personal development process
Before I defend my re-phrasing of Brian’s statement, let me say that he does have a couple of really good ideas in the post (specifically about the dilemma of databases), and I don’t intend to convince you that mocking and stubbing are inherently good ideas that will save the world, or that Brian’s process would be improved by adding mocks and stubs to it.
But Brian makes a broad generalization, attacking ideas that many view as inherently useful in the appropriate context, and I feel that the scope of his statement requires a bit of narrowing.
Thoughts on the Dance-Off
June 7th, 2008
In his Great Test Framework Dance-Off at RailsConf 2008, Josh Susser compared rspec with test/spec and shoulda. All in all I’d say he was very fair in his comparisons and I’d recommend checking out his slides.
There were a couple of dings Josh handed rspec and I’d like to respond to them. This is not intended to sell you on using rspec if you’re not already using it. In fact, you’ll see that I agree with a some of Josh’s criticisms.
I’d much rather see developers using frameworks like test/spec and shoulda than not using anything more expressive than test/unit out of the box, but I also cringe when I hear that someone chose a different framework for reasons that are based on inaccurate or incomplete information. I take full responsibility for that. If you’re basing decisions on inaccurate or incomplete information it’s because I haven’t made the accurate and complete information available to you. This post is one step towards addressing that problem.
RSpec-1.1.2 and ZenTest-3.8.0
January 15th, 2008
The RSpec-1.1.2 release includes changes to keep RSpec compatible with autotest in ZenTest-3.8.0. This new ZenTest release boasts an improved cascading configuration model that works well for subclasses (like those that ship with RSpec) and allows users to override the mappings of specs (or tests) to code as well as the list of files that get ignored by autotest.
To support this, Autotest now loads the following files in the following order:
Autotest AutotestSubClass ~/.autotest ./.autotest
This allows RSpec (or any other library) to override defaults set in Autotest, and then provides users both generic (~/.autotest) and project specific (./.autotest) control over the mappings and exceptions.
How can you take advantage of this?
When autotest begins to run, it calls its :initialize hook. This hook is exposed by the add_hook method. You can use this to access the mappings and exceptions using the following methods on Autotest:
clear_mappings() add_mapping(regexp, proc) remove_mapping(regexp) clear_exceptions() add_exception(string) remove_exception(string)
add_mapping
The add_mapping method adds a key/value pair to a hash that maps regexps to procs. Whenever autotest senses that a file is touched, it looks for the regexp that matches the file name and the runs all the files returned by the associated proc.
Imagine you’re working on a shopping cart app. You have some currency conversion behaviour in a Product model that you’d like to extract to an acts_as_currency plugin, and you want autotest to observe the process. You might add a mapping like this to .autotest:
Autotest.add_hook :initialize do |at| at.add_mapping(%r%^plugins/acts_as_currency/lib/.*\.rb$%) { at.files_matching %r%^spec/models/product_spec\.rb$% + at.files_matching %r%^plugins/acts_as_currency/spec/.*_spec\.rb$% } end
In this case, a change to any of the files in the plugin’s lib directory would cause all the plugins specs to run, as well as the spec for the Product model.
add_exception
The add_exception method adds paths to a list of paths that Autotest ignores.
I like to run autotest in verbose mode (autotest -v) because it tells me when I change a file that it doesn’t know what to do with. The drawback is that it wants to tell me every time I commit because files in the .svn/.hg/.git directories change. So I’ve got these all listed as exceptions in my ~/.autotest file, along with assorted others:
Autotest.add_hook :initialize do |at| %w{.svn .hg .git}.each {|exception|at.add_exception(exception)} end
Note that autotest compiles this list to a Regexp with no anchors, so .hgignore and .gitignore would also get ignored in this case.
Cascading config and granular control
One of the coolest changes in ZenTest-3.8.0 is that autotest loads both ~/.autotest and ./.autotest. So now you can have the hooks you like on every project (like growl notifation) all in one place and still have project specific settings.
This also allows you to set up global mappings/exceptions and modify them at the project level. See Autotest’s RDoc for more info.
RSpec 1.1
December 14th, 2007
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:
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
Running this outputs:
RubyDeveloper using RSpec 1.1.0 - should be able to nest example groupsRubyDeveloper 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.
before_action/after_action
November 6th, 2007
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:
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
And it would be nice to have something that was more expressive using tags like this:
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
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:
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
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:
[: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
This supports controller and view specs (hence including :render).
Please try it out and let me know what you think.

