简体   繁体   English

如何在 Rspec 中测试 Pundit 范围?

[英]How to test Pundit Scopes in Rspec?

I've got a pretty simple Pundit policy with a scope for different user roles.我有一个非常简单的 Pundit 策略,其中包含不同用户角色的范围。 I can't figure out how to test it in Rspec.我不知道如何在 Rspec 中测试它。 Specifically, I don't know how to tell the scope what user is logged in before accessing the scope.具体来说,我不知道如何在访问范围之前告诉范围什么用户登录了。

Here is what I've tried:这是我尝试过的:

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

When I run Rspec, I get:当我运行 Rspec 时,我得到:

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

I use sign_in extensively in controller tests, but I guess that doesn't apply in a Policy test.我在控制器测试中广泛使用sign_in ,但我想这不适用于策略测试。

The Pundit docs says only: Pundit 文档只说:

Pundit does not provide a DSL for testing scopes. Pundit 不提供用于测试范围的 DSL。 Just test it like a regular Ruby class!只需像普通的 Ruby 类一样测试它!

So...does anyone have an example of testing a Pundit scope for a specific user?那么...有没有人为特定用户测试 Pundit 范围的示例? How do I tell the scope what current_user is?如何告诉范围 current_user 是什么?


FWIW, here's the essence of my policy: 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

In my controller, I call it as follows.在我的控制器中,我将其称为如下。 I've confirmed that this works correctly in the real world, with admins seeing all reports, and other users only seeing reports for their account:我已经确认这在现实世界中可以正常工作,管理员可以看到所有报告,而其他用户只能看到他们帐户的报告:

reports = policy_scope(Report)

You can instantiate a policy scope with:您可以使用以下方法实例化策略范围:

Pundit.policy_scope!(user, Report)

Which is short for:简称:

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

Note that you don't need to do any actual steps of signing the user in. user is just an object that your policy scope takes as an initializer argument.请注意,您不需要执行任何使用户登录的实际步骤。 user只是您的策略范围作为初始化参数的对象。 Pundit is after all just plain old OOP. 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

As to the actual spec I would write it as:至于实际规格,我将其写为:

require 'rails_helper'
require 'pundit/rspec' # optional - but includes some nice matchers for policies

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

Avoid constructs such as it { expect ... } and use it blocks that describe the actual behaviour that you are testing or you will end up with really cryptic failure messages and tests that are hard to understand.避免使用it { expect ... }类的结构,并使用 it 块来描述您正在测试的实际行为,否则您最终会得到非常神秘的失败消息和难以理解的测试。 The one-liner syntax it { is_expected.to ... } should only be used to help avoid duplication in situations where the doc string and the matcher used in the example mirror each other exactly. 单行语法it { is_expected.to ... }应该只用于帮助避免在示例中使用的文档字符串和匹配器相互精确镜像的情况下出现重复。

Replacing更换

let(:records) { policy_scope(Report) } 

...with this: ...有了这个:

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

...allows specifying the user to the policy. ...允许为策略指定用户。 No call to sign_in is required.无需调用 sign_in。

Here is the complete solution:这是完整的解决方案:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM