Search code examples
ruby-on-railsrubytestingrspecrspec-rails

Why are instance variables sometimes overused in RSpec tests?


I've noticed examples of instance variable usage in RSpec unit tests etc.

Something like this:

it 'should update something' do
  @user = user(:userone)
  @attr = {
    :name => 'this',
    :phone => 'that'
  }
  put :update, :id => @user.id, :user => @attr

  @user.reload
  expect(response.status).to eq(200)
  expect(@user.name).to eq('this')
  expect(@user.phone).to eq('that')
end

Why not just use local variables if not setting the variable within a before :each etc.?


Solution

  • The overall issue is that variables in tests (like variables in any program) should have the least scope possible. That makes the test easier for readers to understand, since it minimizes the amount of code they have to think about, and it often allows the runtime to be more efficient. So if an instance variable (or, in current RSpec, a let variable) can be a local (without duplicating code or losing efficiency) it should be.

    Back when the best available way to pass data from a before block to an example (an it block) was in an instance variable, I used to see specs like your example all the time. Sometimes they were written by inexperienced programmers who saw a lot of instance variables used in specs and unthinkingly imitated that. (I still see the same error in Rails controllers.) Sometimes they resulted from someone inlining a before block but not taking the trouble to change instance variables that no longer needed to be instance variables into locals.

    These days RSpec provides let. One reason let is nicer than an instance variable is that uses of a let variable look like locals, so you don't need to change them if you change a local to a let variable or vice versa. However, I still often see the problem of test data which is used only in one example being defined in a let variable when it could and should just be a local. Like overuse of instance variables, this sometimes comes from inexperienced programmers unthinkingly imitating examples, and sometimes from a failure to clean up after a change in a test makes a let variable unnecessary.