Test Pundit’s headless policies with rspec
I recently started writing policy classes with Pundit for the comix web application. Dashboards controller is a classic case where I need to control the access of the page based on a user role.
My setup includes using devise for authentication, rolify gem for assigning roles to a user and pundit for policy management. Pretty standard so far. I however need to test my policy classes. I came across this article from Thunderbolt labs which talked about setting up pundit’s rspec helper with shoulda matchers. It seems like the cleanest way to test my policy classes. The syntax however requires a bit of an update since some of the methods they’re using in the helper class are deprecated. Here’s how my policy_helper.rb looks like :
RSpec::Matchers.define :permit do |action|
match do |policy|
policy.public_send("#{action}?")
endfailure_message do |policy|
"#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
endfailure_message_when_negated do |policy|
"#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
end
end
As you can see failure_message_for_should and failure_message_for_should_not have since been deprecated. Now, my headless policy is a struct and assumes there is no model backing the controller.
class DashboardsPolicy < Struct.new(:user, :dashboards)
def index?
user.has_role? :creator
end
end
Setting up authorization in Dashboards controller is pretty straightforward too.
class DashboardsController < ApplicationController
before_action :authenticate_user!
def index
authorize :dashboards, :index?
end
end
Now, the spec should test whether the controller action is permitted using that policy or not for a particular user. In order to do that we’ll use the shoulda matchers we created earlier. We initialize a user object and pass it to the dashboard policy. Policy then validates the role and returns a boolean for us to work with.
require 'rails_helper'RSpec.describe DashboardsPolicy do
subject { DashboardsPolicy.new(user) }
context "for a buyer" do
let(:user) { create(:user) }
it { should_not permit(:index) }
end
context "for a creator" do
let(:user) { create(:user, :creator) }
it { should permit(:index) }
end
end