Testing Pundit Policies with RSpec
March 27, 2013The fine folks over at Elabs recently released Pundit: a new authorization gem that uses “regular Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorization system”. It competes squarely with cancan, but aims at being simpler, more predictable and easier to understand.
We’re going to look at a way of making your pundit specs as readable as possible, but first…
A Quick Pundit Overview
With pundit, you create regular ruby policy classes that follow well defined conventions. Here’s an example of a policy file that would control access to an Article
class. This goes in the app/policies/article_policy.rb
file:
class ArticlePolicy < ApplicationPolicy
def show?
true
end
def create?
user
end
def update?
user
end
def destroy?
user
end
...
end
While ArticlePolicy
descends from ApplicationPolicy
, that doesn’t make it special. In fact, ApplicationPolicy
is simply another Ruby class:
class ApplicationPolicy < Struct.new(:user, :record)
def show?
true
end
def create?
false
end
...
end
Note that we’re descending from Struct, which gives us #user
and #record
accessors for free. Pundit then helps you make use of these classes inside your views and controllers.
Now, let’s look at how you’d test drive these with the help of…
A Custom Matcher for Pundit
While it’s entirely reasonable to test your pundit policies using stock rspec, it’s always nice to be able to increase the readability of your specs with a well written custom matcher.
Here’s what we’d like the specs to look like. This is the spec/policies/article_policy_spec.rb
spec file:
require 'spec_helper'
describe ArticlePolicy do
subject { ArticlePolicy.new(user, article) }
let(:article) { FactoryGirl.create(:article) }
context "for a visitor" do
let(:user) { nil }
it { should permit(:show) }
it { should_not permit(:create) }
it { should_not permit(:new) }
it { should_not permit(:update) }
it { should_not permit(:edit) }
it { should_not permit(:destroy) }
end
context "for a user" do
let(:user) { FactoryGirl.create(:user) }
it { should permit(:show) }
it { should permit(:create) }
it { should permit(:new) }
it { should permit(:update) }
it { should permit(:edit) }
it { should permit(:destroy) }
end
end
Note two things:
- The use of late-binding
let
statements foruser
andarticle
. This lets us change those values when we create theArticlePolicy.new(user, article)
subject inside each of the context blocks. - The
should permit(:action)
syntax, which is enabled through this custom matcher:
RSpec::Matchers.define :permit do |action|
match do |policy|
policy.public_send("#{action}?")
end
failure_message_for_should do |policy|
"#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
end
failure_message_for_should_not do |policy|
"#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
end
end
Throw that in a file under spec/support
(we named ours spec/support/pundit_matcher.rb
), and make sure yourspec/spec_helper.rb
includes it:
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
For Security’s Sake
Readable code and easily understandable specs are important in every project and every context, but they’re absolutely paramount when dealing with authorization rules. Between Pundit and the power of custom RSpec matchers, we can now spot holes in our auth policies with ease.