Search code examples
ruby-on-railsrspecauthorizationpundit

Rails Pundit Policy Spec test failing with NoMethodError


I have just started using Pundit for authorization in my current project along with the pundit-matchers gem.

So far it seems to generally be working for me but I have a problem in my tests.

I have generally tried to follow the examples in the pundit-matchers readme and the Thunderbolt labs blog (http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/).

This is my policy file;

#app/policies/procedure_policy.rb
class   ProcedurePolicy
    attr_reader :user, :procedure

    def initialize(user, procedure)
        @user = user
        @procedure = procedure
    end

    def index?
        user.admin?
    end

end

And this is my policy_spec file

require 'rails_helper'

describe ProcedurePolicy do
    subject {described_class.new(user, procedure)}

    let(:procedure) {FactoryGirl.create(:procedure)}

    context "for a guest" do
        let(:user) {nil}
        it {is_expected.not_to permit_action(:index)}
    end

    context "for a non-admin user" do
        let(:user) {FactoryGirl.create(:user)}
        it {is_expected.not_to permit_action(:index)}
    end

    context "for an admin user" do
        let(:user) {FactoryGirl.create(:admin_user)}
        it {is_expected.to permit_action(:index)}
    end

end

2 of my 3 tests pass; The "for a non-admin user" and "for an admin user" ones. The "for a guest" test fails with

NoMethodError:
       undefined method `admin?' for nil:NilClass

Now I understand why. I'm passing nil to the #index? method of my ProcedurePolicy class which will not have an #admin? method. But all of the example specs I have found online do exactly this. What am I not seeing.

Apologies if I'm missing something really obvious. I've been away from coding for a couple of years.


Solution

  • The "being a visitor" context is for testing authorisation attempts when there is no user currently authenticated in the system. Since no user is logged in, no user record/object exists - the absence of a user object is nil. This is why both the Thunderbolt Labs and pundit-matchers examples use nil to represent a visitor. The nil object does not have an admin? method, causing the error.

    To correctly handle nil/guest users in your policy, check that the user object is present before checking if the user is an admin, either by checking the user object directly or using the present? method in Rails, e.g.

    def index?
      user.present? && user.admin?
    end
    

    or just:

    def index?
      user && user.admin?
    end
    

    You'll find that authentication systems such as Devise return nil from the current_user method when no user is signed in.