Search code examples
ruby-on-railsrspecrspec-railspundit

Is there any way to mock Pundit policy authorize in request specs?


I am using Pundit for authorization on my Rails app and I am unit testing my requests. I have already successfully tested the policy but now I want to verify that my request is using that policy. I want to do it in a generic way, that I can use in ANY request spec (regardless of the controller, the action, and the policy). To be honest at this point I would be conformed with a way of saying "expect any policy to receive authorize", generic to all requests.

For the index action (that uses the policy scope) it was easy:

In the request describe I say

RSpec.describe 'GET /companies', type: :request do
  include_context 'invokes policy scope'
end

And then I defined the shared context as:

RSpec.shared_context 'invokes policy scope', shared_context: :metadata do
  before do
    expect(Pundit).to receive(:policy_scope!) do |user_context, relation|
      expect(user_context.user).to eq(user)
      expect(user_context.current_group).to eq(group)

      relation
    end
  end
end

But how do I do the same for the authorize method. I don't know which concrete policy will be the one receiving it, neither which is the concrete controller that will invoke the authorize.

I don't understand how pundit does not provide a custom matcher to verify a certain request is actually invoking a given policy, or to "simulate" authorized/unauthorized scenarios so I test that my request returns the correct status code.

Any ideas?


Solution

  • when you include Pundit module in your controllers then the policy_scope, and the autherize methods become available inside your controller as public methods.

    so when you send an get or post request to your controller rails behind the scenes creates an instance of the controller ControllerClass.new so what you need is to mock the authorize method on the instantiated controller object.

    to mock the method on that object you need to know or have that object in your tests which is not possible. but hopefully you can mock a public method on any class instance in advance.

    so to mock the authorize method you will write :

    expect_any_instance_of(described_class).to receive(:authorize).with(any_params_you_want).and_return(nil)
    

    expect_any_instance_of is a method provided by rspec to mock any instantiated object of a certain class. click here to learn more

    so no need to reference Pundit class in your tests anymore. actually that would create a dependency in our tests on the class name of the gem, which you do not need since you can test both methods as explained above.