I have a request spec describing an endpoint behavior. In the root context, I have:
before { patch '/api/v2/users/meberships', request.to_json, auth_headers(current_user) }
One of my test contexts let! creates a credential making current_user an owner.
When the test runs, the before executes prior to the let! and fails.
I can get the behavior I want by touching the credential in the before block, so the let! is behaving like let.
RSpec.describe URI('/api/v2/users/'), type: :request do
let(:current_user) { Fabricate(:user) }
let(:org) { Fabricate(:organzantion) }
context 'Failing PATCH to /memberships' do
let(:request) { {} }
before do
patch '/api/v2/users/meberships', request.to_json, auth_headers(current_user)
end
shared_examples 'a valid response' do
specify { controller.should respond_with :created }
end
context 'as an organization owner' do
let!(:credential) { Fabricate(:credential, user: current_user, organization: org, role: :owner) }
it_should_behave_like 'a valid response' # fails because credential hasn't been fabricated
end
end
context 'Working PATCH to /memberships' do
let(:request) { {} }
let(:credential) {}
before do
credential # late bound
patch '/api/v2/users/meberships', request.to_json, auth_headers(current_user)
end
shared_examples 'a valid response' do
specify { controller.should respond_with :created }
end
context 'as an organization owner' do
let!(:credential) { Fabricate(:credential, user: current_user, organization: org, role: :owner) }
it_should_behave_like 'a valid response' # succeeds
end
end
end
In general, before and let! run in declaration order, e.g.:
before ( puts "before" }
let!(:foo) { puts "let!" }
Would print:
let!
...whereas this:
let!(:foo) { puts "let!" }
before ( puts "before" }
...would print:
let!
before
...so you are in complete control of when let! and before get executed.
In your case, that doesn't actually help, because your let! declaration is nested more deeply relative to the before. RSpec _always_ evaluates things outer context to inner context. This is the way things have always worked, and supporting running things from the inside out would be very confusing.
In general, I recommend against:
let! when the exact order in the setup it runs is important. When ordering is important, it's far better to be _explicit_ about it, and do the actions in sequential order in a single block by sequentially calling the appropriate methods. Otherwise you risk a future refactoring to your spec subtlety changing the order and either causing your spec to fail, or worse -- turning it into a false positive.before block. before is intended to help with common setup needs (the "arrange" part of the arrange/act/assert pattern). Using before for the "act" part (e.g. making the HTTP request) causes this problem where you've got a particular example that needs some additional setup before the "act" but is prevented from doing so by the fact that the "act" has been put in a before hook. For better to just extract a useful helper method (e.g. make_request) and calling that at the appropriate places in your examples.
Most helpful comment
In general,
beforeandlet!run in declaration order, e.g.:Would print:
...whereas this:
...would print:
...so you are in complete control of when
let!andbeforeget executed.In your case, that doesn't actually help, because your
let!declaration is nested more deeply relative to thebefore. RSpec _always_ evaluates things outer context to inner context. This is the way things have always worked, and supporting running things from the inside out would be very confusing.In general, I recommend against:
let!when the exact order in the setup it runs is important. When ordering is important, it's far better to be _explicit_ about it, and do the actions in sequential order in a single block by sequentially calling the appropriate methods. Otherwise you risk a future refactoring to your spec subtlety changing the order and either causing your spec to fail, or worse -- turning it into a false positive.beforeblock.beforeis intended to help with common setup needs (the "arrange" part of the arrange/act/assert pattern). Usingbeforefor the "act" part (e.g. making the HTTP request) causes this problem where you've got a particular example that needs some additional setup before the "act" but is prevented from doing so by the fact that the "act" has been put in abeforehook. For better to just extract a useful helper method (e.g.make_request) and calling that at the appropriate places in your examples.