【问题标题】:How to test Pundit Scopes in Rspec?如何在 Rspec 中测试 Pundit 范围?
【发布时间】:2019-01-23 15:27:36
【问题描述】:

我有一个非常简单的 Pundit 策略,其中包含不同用户角色的范围。我不知道如何在 Rspec 中测试它。具体来说,我不知道如何在访问范围之前告诉范围什么用户登录了。

这是我尝试过的:

let(:records) { policy_scope(Report) } 

context 'admin user' do
  before(:each) { sign_in(admin_user) }
  it { expect(reports.to_a).to match_array([account1_report, account2_report]) }
end

context 'client user' do
  before(:each) { sign_in(account2_user) }
  it { expect(reports.to_a).to match_array([account2_report]) }
end

当我运行 Rspec 时,我得到:

NoMethodError: undefined method `sign_in' for #<RSpec::ExampleGroups::ReportPolicy::Scope:0x00007f93241c67b8>

我在控制器测试中广泛使用sign_in,但我猜这不适用于策略测试。

Pundit 文档只说:

Pundit 不提供用于测试范围的 DSL。只需像普通的 Ruby 类一样测试它!

那么...有没有人为特定用户测试 Pundit 范围的示例?如何告诉范围 current_user 是什么?


FWIW,这是我政策的精髓:

class ReportPolicy < ApplicationPolicy
  def index?
    true
  end

  class Scope < Scope
    def resolve
      if user.role == 'admin'
        scope.all
      else
        scope.where(account_id: user.account_id)
      end
    end
  end
end

在我的控制器中,我将其称为如下。我已经确认这在现实世界中可以正常工作,管理员可以看到所有报告,而其他用户只能看到他们帐户的报告:

reports = policy_scope(Report)

【问题讨论】:

    标签: ruby-on-rails ruby rspec pundit


    【解决方案1】:

    您可以通过以下方式实例化策略范围:

    Pundit.policy_scope!(user, Report)
    

    简称:

    ReportPolicy::Scope.new(user, Report).resolve
    

    请注意,您不需要执行任何实际步骤来让用户登录。user 只是您的策略范围作为初始化参数的对象。 Pundit 毕竟只是普通的旧 OOP。

    class ApplicationPolicy
      # ...
      class Scope
        attr_reader :user, :scope
    
        def initialize(user, scope)
          @user = user
          @scope = scope
        end
    
        def resolve
          scope.all
        end
      end
    end
    

    至于实际的规格,我会写成:

    require 'rails_helper'
    require 'pundit/rspec'
    
    RSpec.describe ReportPolicy, type: :policy do
      let(:user) { User.new }
      let(:scope) { Pundit.policy_scope!(user, Report) } 
      # ... setup account1_report etc
    
      describe "Scope" do
        context 'client user' do
          it 'allows a limited subset' do
            expect(scope.to_a).to match_array([account2_report])
          end 
        end
        context 'admin user' do
          let(:user) { User.new(role: 'admin') }
          it 'allows access to all the reports' do
            expect(scope.to_a).to match_array([account1_report, account2_report])
          end
        end
      end
    end
    

    避免使用诸如it { expect ... } 之类的结构,并使用它来描述您正在测试的实际行为的块,否则您最终会收到非常神秘的失败消息和难以理解的测试。 one-liner syntax it { is_expected.to ... } 应该只用于帮助避免在示例中使用的文档字符串和匹配器相互精确镜像的情况下出现重复。

    【讨论】:

    【解决方案2】:

    替换

    let(:records) { policy_scope(Report) } 
    

    ...用这个:

    let(:records) { ReportPolicy::Scope.new(user, Report).resolve }
    

    ...允许为策略指定用户。无需调用 sign_in。

    这里是完整的解决方案:

    let(:records) { ReportPolicy::Scope.new(user, Report).resolve }
    
    context 'admin user' do
      let(:user) { admin_user }
      it { expect(reports.to_a).to match_array([account1_report, account2_report]) }
    end
    
    context 'client user' do
      let(:user) { account2_user }
      it { expect(reports.to_a).to match_array([account2_report]) }
    end
    

    【讨论】:

      猜你喜欢
      • 2014-05-18
      • 1970-01-01
      • 2017-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-23
      • 2017-04-26
      相关资源
      最近更新 更多