21

Here is a bit of code from M Hartl's Ruby on Rails Tutorial. Can anyone explain why an instance variable (@user) is necessary and why not use a local variable. Also, since instance variables are supposed to be the variables in the instance of a class, which class is @user instantiated from?

require 'spec_helper'

describe User do

  before { @user = User.new(name: "Example User", email: "user@example.com") }

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
end
Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
TradeRaider
  • 754
  • 4
  • 8
  • 21

3 Answers3

34

Use of a local variable in that instance would mean that its scope would be restricted to the before and hence result in an error. The @user is of type User but is an instance variable of the describe block. Rspec has some magic that at run-time makes a class out of each describe block. Each example (it block) ends up being a subclass of said class. Class inheritance lets the examples see @user.

Edited 2017-05-14

Linked blog post is no longer available. Updating with Wayback Machine link + inlining relevant section here.

Note that this is considered an anti-pattern as detailed in this blog post. Use let instead.

let has the following advantages:

  • It is memoized when used multiple times in one example, but not across examples.
  • It is lazy-loaded, so you wont waste time initializing the variable for examples that don't reference it.
  • Will raise an exception if you have a typo in your variable name.
peakxu
  • 6,667
  • 1
  • 28
  • 27
  • 1
    Oh, I think I get it now. I always thought describe block was used to make the tests more readable to humans and nothing else. Thanks :) – TradeRaider Sep 28 '12 at 18:37
  • That blog post is no longer available. It might be worth updating the answer to include reasons why using an instance variable is an antipattern and remove the broken link. – keoghpe May 10 '17 at 10:02
12

You can't use a local variable because a local variable exists only in the scope of the local method. before, subject and it generates different scopes within the same class.

The following code

before { user = User.new(name: "Example User", email: "user@example.com") }

will raise an undefined variable when you call it in

subject { user }

The instance @user is an instance of the class User (after all, you create it with User.new).

However, instead of instance variables you might want to use the let command. Also, if you define

subject { User.new(name: "Example User", email: "user@example.com") }

the use of before is not required. You'll also get the extra benefit to get a subject method available to access the instance, equal to define let(:subject).

Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
2

subject ad it blocks are under different scopes, so local variables won't work. @user belongs to class generated by RSpec under the hood.

Victor Deryagin
  • 11,895
  • 1
  • 29
  • 38