How to declare a variable shared between examples in RSpec?

Suppose I have the following spec:

...
describe Thing do

  it 'can read data' do
     @data = get_data_from_file  # [ '42', '36' ]
     expect(@data.count).to eq 2
  end

  it 'can process data' do
     expect(@data[0].to_i).to eq 42  # Fails because @data is nil
  end

end
...

All I wanted is to have a variable shared in the given describe or context. I would write a value in one example and read it in another. How do I do that?


Solution 1:

You should use before(:each) or before(:all) block:

describe Thing do
  before(:each) do
    @data = get_data_from_file  # [ '42', '36' ]
  end

  it 'can read data' do
    expect(@data.count).to eq 2
  end

  it 'can process data' do
    expect(@data[0].to_i).to eq 42
  end
end

The difference is that before(:each) will be executed for each case separately and before(:all) once before all examples in this describe/context. I would recommend you to prefer before(:each) over before(:all), because each example will be isolated in this case which is a good practice.

There are rare cases when you want to use before(:all), for example if your get_data_from_file has a long execution time, in this case you can, of course, sacrifice tests isolation in favor of speed. But I want to aware you, that when using before(:all), modification of your @data variable in one test(it block) will lead to unexpected consequences for other tests in describe/context scope because they will share it.

before(:all) example:

describe MyClass do
  before(:all) do
    @a = []
  end

  it { @a << 1; p @a }
  it { @a << 2; p @a }
  it { @a << 3; p @a }
end

Will output:

[1]
[1, 2]
[1, 2, 3]

UPDATED

To answer you question

describe MyClass do
  before(:all) do
    @a = []
  end

  it { @a = [1]; p @a }
  it { p @a }
end

Will output

[1]
[]

Because in first it you are locally assigning instance variable @a, so it isn't same with @a in before(:all) block and isn't visible to other it blocks, you can check it, by outputting object_ids. So only modification will do the trick, assignment will cause new object creation.

So if you are assigning variable multiple times you should probably end up with one it block and multiple expectation in it. It is acceptable, according to best practices.

Solution 2:

This is really the purpose of the RSpec let helper which allows you to do this with your code:

...
describe Thing do
  let(:data) { get_data_from_file }

  it 'can read data' do
     expect(data.count).to eq 2
  end

  it 'can process data' do
     expect(data[0].to_i).to eq 42
  end

end
...