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?
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.
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.
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.
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.
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_id
s. 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.
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
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With