Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 486
Andrey Esperanza Avatar asked Dec 28 '14 18:12

Andrey Esperanza


People also ask

What is shared context in RSpec?

Shared Contexts in RSpec In RSpec, every example runs in a context: data and configuration for the example to draw on, including lifecycle hooks, let and subject declarations, and helper methods. These contexts are inherited (and can be overridden) by nested example groups.

How do I set an instance variable in RSpec?

If you want to set a controller or view's instance variables in your RSpec test, then call assign either in a before block or at the start of an example group. The first argument is the name of the instance variable while the second is the value you want to assign to it.

What does let do in RSpec?

The let method should be called inside an example group. The first argument is the name of a variable to define. The let method is passed a block that computes the value of the variable, and the block will be called if the value of the variable is ever needed. In other words, let variables are lazily evaluated.

What is context in RSpec?

According to the rspec source code, “context” is just a alias method of “describe”, meaning that there is no functional difference between these two methods. However, there is a contextual difference that'll help to make your tests more understandable by using both of them.


2 Answers

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.

like image 153
Rustam A. Gasanov Avatar answered Oct 12 '22 12:10

Rustam A. Gasanov


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
...
like image 16
Anthony Avatar answered Oct 12 '22 11:10

Anthony