How to declare a variable shared between examples in RSpec?
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 endend
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 endend...
I just ran into this same problem. How I solved it was by using factory_girl gem.
Here's the basics:
create a factory (here's a code snippet:
require 'factory_girl'require 'faker' # you can use faker, if you want to use the factory to generate fake dataFactoryGirl.define do factory :generate_data, class: MyModule::MyClass do key 'value' endend
Now after you made the factory you need to make a Model that looks like this:
Module MyModule class MyClass attr_accessor :key #you can also place methods here to call from your spec test, if you wish # def self.test #some test # end endend
Now going back to your example you can do something like this:
describe Thing do before(:all) do @data = FactoryGirl.build(:generate_data) end it 'can read data' do @data.key = get_data_from_file # [ '42', '36' ] expect(@data.key.count).to eq 2 end it 'can process data' do expect(@data.key[0].to_i).to eq 42 # @data will not be nil. at this point. whatever @data.key is equal to last which was set in your previous context will be what data.key is here endend
Anyways, good luck let us know, if you got some other solution!