How to Declare a Variable Shared Between Examples in Rspec

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
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.

How to share a variable with many it examples in rspec

You can use lets within lets:

context "User" do
let(:email_address) { 'test@test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }

You will then also have access to the email_address variable within all your tests.

This works because previously the email address was being randomly generated by the factory every time the user was created, as we hadn't set a value for it anywhere. So, we called the code below in each test:

send_preferences(user, "new")

It called the 'user' let which created a new user with a completely random email address (as we hadn't give it a specific email value). Therefore during the backend API call it was sending a different email address every time.

let(:user) { FactoryGirl.create(:user) }

However, when we defined the email address 'let' as 'test@test.com', and passed that into the user factory as in the code I provided, we overrode the randomly generated email address with our own static value, So, every time we call the code again:

send_preferences(user, "new") 

It now triggers the user factory create which is also taking our new 'email_address' let, which is always set to a specific value of test@test.com every time it is called.

let(:email_address) { 'test@test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }

Therefore, when the backend API call is made the email address is always what we set it to.

Also, as it is a let we can use that variable in any of the tests themselves if we wish. For example:

it 'should set the email address' do
expect(user.email_address).to eq(email_address)
end

It's quite hard to explain in a few sentences but let me know if that's still not clear.

How to pass an instance variable to an RSpec shared example

The problem is that self within the example group is different from self within a before hook, so it's not the same instance variable even though it has the same name.

I recommend you use let for cases like these:

# support/shared_examples.rb
shared_examples "a text field" do |field, fill, length|
it "it should be long enough" do
model.send("#{field}=", fill * length)
model.should be_valid
end
end

# company_spec.rb
describe Company do
describe "when address2" do
it_behaves_like "a text field", "address2", "a", Company.address2.limit do
let(:model) { Company.new( init stuff here ) }
end
end
end

RSpec Cannot Use Defined Variables in Shared Examples

The problem here is that the memoized let helper show_params is called outside of an example.

Instead of passing the params you can simply reference a let from the outer scope where you are including the example:

RSpec.describe UsersController, type: :controller do
let(:user) { FactoryGirl.create(:user) }
describe "GET #show" do
let(:action) { get :show, id: user }
it_should_behave_like "an authorized action"
end
end

RSpec.shared_examples "an authorized action" do
it "denies access" do
action
expect(response).to redirect_to(signin_url)
end
end

This is a pretty powerful pattern that lets you use a convention over configuration approach since the last let always wins.

RSpec.describe UsersController, type: :controller do
let(:user) { FactoryGirl.create(:user) }
describe "GET #show" do
let(:action) { get :show, id: user }
it_should_behave_like "an authorized action"

context "when signed in" do
before { sign_in user }
let(:action) { get :show, id: other_user }
context 'when viewing another user' do
it_should_behave_like "an authorized action"
end
end
end
end

RSpec: How to pass a let variable as a parameter to shared examples

From the docs, I think you need to put your let statement in a block passed to it_behaves_like:

let(:data) { "saved data" }

shared_examples "saves data to right place" do
it { expect(right_place.data).to eq data }
end

context "when something it saves to one place" do
it_behaves_like "saves data to right place" do
let(:right_place) { create(:place) }
end
end

context "when whatever it saves to other place" do
it_behaves_like "saves data to right place" do
let(:right_place) { create(:place) }
end
end

How to share RSpec let variables between shared contexts?

I'm not aware of a way to refer to the currently defined value of a let variable in a let block. (When I try it I get "stack level too deep".) I'd do what you're trying to do this way:

Rspec.shared_context 'JSON request' do
let(:common_headers) do
{
'Accept' => 'application/json'
}
end
let(:headers) { common_headers }
end

Rspec.shared_context 'Authenticated request' do
let(:headers) do
common_headers.merge('Authorization' => "Bearer #{token}")
end
end

How do I pass a variable two levels in a nested Rspec shared example?

You have recursion in your code:

let(:variable) { variable }

will call itself over and over again


Specs will pass down their variables automatically, so this will work:

shared_examples 'Foo' do
it_behaves_like 'Bar'
it_behaves_like 'Baz'
end

shared_examples 'Bar' do
it { expect(variable).to eq('value') }
end

and

describe SomeClass do
let(:variable) { 'value' }
it_behaves_like 'Foo'
end


Related Topics



Leave a reply



Submit