Alex Rivera | Logout

When to use RSpec let()?

Asked 2011-03-19T02:10:04.403
480

I tend to use before blocks to set instance variables. I then use those variables across my examples. I recently came upon let(). According to RSpec docs, it is used to

... to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.

How is this different from using instance variables in before blocks? And also when should you use let() vs before {}?

Edit
Report

2 Answers

632

I always prefer let to an instance variable for a couple of reasons:

  • Instance variables spring into existence when referenced. This means that if you fat finger the spelling of the instance variable, a new one will be created and initialized to nil, which can lead to subtle bugs and false positives. Since let creates a method, you'll get a NameError when you misspell it, which I find preferable. It makes it easier to refactor specs, too.
  • A before(:each) hook will run before each example, even if the example doesn't use any of the instance variables defined in the hook. This isn't usually a big deal, but if the setup of the instance variable takes a long time, then you're wasting cycles. For the method defined by let, the initialization code only runs if the example calls it.
  • You can refactor from a local variable in an example directly into a let without changing the referencing syntax in the example. If you refactor to an instance variable, you have to change how you reference the object in the example (e.g. add an @).
  • This is a bit subjective, but as Mike Lewis pointed out, I think it makes the spec easier to read. I like the organization of defining all my dependent objects with let and keeping my it block nice and short.

A related link can be found here: http://www.betterspecs.org/#let

answered 2011-03-19T04:04:30.607
6

let is functional as its essentially a Proc. Also its cached.

One gotcha I found right away with let... In a Spec block that is evaluating a change.

let(:object) {FactoryGirl.create :object}

expect {
  post :destroy, id: review.id
}.to change(Object, :count).by(-1)

You'll need to be sure to call let outside of your expect block. i.e. you're calling FactoryGirl.create in your let block. I usually do this by verifying the object is persisted.

object.persisted?.should eq true

Otherwise when the let block is called the first time a change in the database will actually happen due to the lazy instantiation.

Update

Just adding a note. Be careful playing code golf or in this case rspec golf with this answer.

In this case, I just have to call some method to which the object responds. So I invoke the _.persisted?_ method on the object as its truthy. All I'm trying to do is instantiate the object. You could call empty? or nil? too. The point isn't the test but bringing the object ot life by calling it.

So you can't refactor

object.persisted?.should eq true

to be

object.should be_persisted 

as the object hasn't been instantiated... its lazy. :)

Update 2

leverage the let! syntax for instant object creation, which should avoid this issue altogether. Note though it will defeat a lot of the purpose of the laziness of the non banged let.

Also in some instances you might actually want to leverage the subje

answered 2013-05-07T19:32:35.970

Your Answer