如何在 Rspec 中测试 Pundit Scopes?
How to test Pundit Scopes in Rspec?
我有一个非常简单的 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 does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
所以...有人有针对特定用户测试 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)
正在替换
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
您可以实例化策略范围:
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' # 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
避免使用 it { expect ... }
等结构,并使用它来描述您正在测试的实际行为,否则您最终会得到非常神秘的失败消息和难以理解的测试。 one-liner syntax it { is_expected.to ... }
应该只用于帮助避免在示例中使用的文档字符串和匹配器完全相互镜像的情况下重复。
我有一个非常简单的 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 does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
所以...有人有针对特定用户测试 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)
正在替换
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
您可以实例化策略范围:
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' # 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
避免使用 it { expect ... }
等结构,并使用它来描述您正在测试的实际行为,否则您最终会得到非常神秘的失败消息和难以理解的测试。 one-liner syntax it { is_expected.to ... }
应该只用于帮助避免在示例中使用的文档字符串和匹配器完全相互镜像的情况下重复。