So, I have a few comments...
Expectations (expect
statements) should be inside an example (it
) block and not inside a before
block. The kind of thing that goes in a before
block are allow statements (e.g., allow(ClassX).to receive(:method) { object }
), data modification that can't be done in a test variable declaration, or request triggers. See http://www.betterspecs.org/ for some examples. TL;DR, that shared context is not an appropriate way to test.
The way to test that policy_scope is being called with specific params is:
# You can put something generic like this in a shared context and then
# define 'params' and 'scoped_result' as let vars in the specs that include
# the shared context
let(:request) { get '/companies' }
let(:params) { user_context or whatever }
let(:scoped_result) { relation }
# By using abstract variable names here, we make this reusable
it 'calls policy scope' do
expect(Pundit).to receive(:policy_scope!).with(params)
request
end
it 'scopes result' do
expect(Pundit.policy_scope!(params)).to eq(scoped_result)
end
To mock it and stub its responses, you would do:
before do
# This ensures Pundit.policy_scope!(context) always returns scoped_result
allow(Pundit).to receive(:policy_scope!).with(context) { scoped_result }
end
...but these are extremely bad/brittle tests, especially as it pertains to a request spec. Your Pundit policies should already be tested in the policy spec files (see https://github.com/varvet/pundit#rspec), so what you really want to be doing is testing that your endpoint returns the correct output (scoped response) given a certain input (authenticated policy-managed-object). It's a bad idea to try and override the functionality of Pundit by mocking the response because your endpoint's specs will continue passing if you make a breaking change to your policy code. What you want to do here is set up test variables to suit the circumstances that would result in a successful request, but make sure everything is generic so it can be reused. For request specs, you could do something like:
# Shared context stuff
let(:json) { JSON.parse(response.body) }
let(:headers) { ...define the headers to use across requests...}
before { request }
shared_examples_for 'success' do
it { expect(response).to have_http_status(:success) }
it { expect(json).to eq(expected) } # or something
end
# User spec that includes shared context
include_context 'above stuff'
let(:request) { get '/companies', params: params, headers: headers }
let(:params) { { user_id: user.id } } # or something
let!(:admin_thing) {
...something that should be excluded by the pundit policy used by endpoint...
}
context 'restricted' do
let!(:user) { create :user, :restricted }
let(:expected) { ...stuff scoped to restricted user... }
it_behaves_like 'success'
end
context 'manager' do
let!(:user) { create :user, :manager }
let(:expected) { ...stuff scoped to manager user... }
it_behaves_like 'success'
end
context 'superuser' do
let!(:user) { create :user, :superuser }
let(expected) { ...unscoped stuff visible to super user... }
it_behaves_like 'success'
end
Notice that, at the higher level (shared context), the name and function are generic. At the lower level (the spec declaring permissioned users), the spec translates the abstract names into values specific to the endpoint being tested. The spec also creates an additional object that should not be returned by the policy scope (essentially testing the scoping by confirming that this object is excluded from the result). Hope that helps.