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.
a convention emerges
One convention that has emerged over time is to introduce a helper method, like so:
describe BowlingGame do def game @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
With the first call to the game method in each example, ruby invokes the right side of the expression and stores it in the @game variable on the left, which is returned to the current and each subsquent caller. This approach takes up the same number of lines as the before(:each) block, but note the lack of @ symbols in the examples themselves.
Consider that there will likely be several more examples that have a similar structure, and you can see that the removal of @ symbols reduces a fair amount of noise. Not that there’s anything inherently wrong or noisy about instance variables, but for many this may just feel cleaner.
from convention emerges solution
Thanks to a suggestion from Stuart Halloway, rspec-1.2.9 introduces a new let() method that lets examples move towards @-less-ness by encapsulating the process of caching the instance variable. Here’s the previous example using let():
describe BowlingGame do let(:game) { BowlingGame.new } 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
The call to let() in this example defines a game method. The first time game is called in each example, it invokes the block, caches the result (an instance of BowlingGame), and returns it. Each subsequent call to game returns the same object, just like the game method in the previous example.
This will, admittedly, not change your life. Nor will it appeal to all of you, though I expect it to appeal to the lispier among you. The thing that I’ve noticed is that as more of these appear in a codebase, the more meaning they begin to establish, and it actually helps to compartmentalize this pattern and separate it from other helper methods that might be floating around.
So experiment away and please feel free to provide feedback here or on the rspec-users mailing list.


February 6th, 2010 at 12:14 pm
I am for the old
@game.score.should == 0
It is like in rails where in views you easily recognise variables that comes from controllers. This way the “before(:each)” tastes like the controller of the test.
You say “you can see that the removal of @ symbols reduces a fair amount of noise” but I feel like it obscures the code since you loose the immediate knowlege of where variables came from.
February 8th, 2010 at 1:57 pm
Pieppo - you can still do that and this is certainly subjective. There are those who find sharing instance variables across scopes to be more confusing, even in Rails controllers: http://github.com/voxdolo/decent_exposure
June 16th, 2010 at 2:20 pm
I have completely different question. You’re saying that using this: def game @game ||= BowlingGame.new end
is same as using: let(:game) { BowlingGame.new }
But isn’t the caching in here a bad thing? Doesn’t it cause possible dependencies between examples? Or is this encouraged to do?
At least i have tried to keep dependencies between examples as minimal as possible, thus creating new bowling game for each example seems to be natural for me.
What if you have 20 examples and they’re all dependent from each other and then the second example will fail - there is a chance that you’ll see 18 failures and unable to tell right from the failures themselves if it’s because of a dependency or not. If you think that this is okay, then why not let tests continue if some “should” fails?
I would like to get your opinion on this.
June 22nd, 2010 at 2:38 pm
Jarmo, I’ve been worried too about that but, confirmed, it seems to work even if you change state! I’m not sure I understand how it does that
class Emo attr_accessor :mood def initialize @mood = :sad end end
describe Emo do
let(:emo) { Emo.new }
it “should be sad” do emo.mood.should == :sad emo.mood = :happy end
it “should still be sad” do emo.mood.should == :sad end
end
2 examples, 0 failures
June 24th, 2010 at 1:24 pm
@jarmo and @txus - the caching is per-example, not across examples.
June 27th, 2010 at 6:05 pm
Oh, that makes sense now. So, even if
def game @game ||= Game.new end
is used, then the variable @game is created each time as it’s accessed first time from each example.
@txus, if we think that each example is a separate instance of an example class (whatever it’s exact class name was) and each class has it’s own instance variables then it doesn’t matter if you change object’s state in one example or another.
Now i understand why this approach hasn’t worked for me. That’s because i have used before :all instead of before :each and that means that the underlying variable is same for each example thus causing dependencies.
Thanks for this good lisp’ish trick anyway for removing @ signs from the specs
It might be useful.