如何使用 Shoulda 正确检查唯一性和范围

How to correctly check uniqueness and scope with Shoulda

我有一个 User 模型,其子关联为 items:name 项目对用户来说应该是唯一的,但它应该允许不同的用户拥有同名的项目。

项目模型当前设置为:

class Item < ApplicationRecord
  belongs_to :user
  validates :name, case_sensitive: false, uniqueness: { scope: :user }
end

这可以在用户内部验证,但仍然允许其他用户保存同名的项目。

如何使用 RSpec/Shoulda 进行测试?

我现在的测试是这样写的:

describe 'validations' do
    it { should validate_uniqueness_of(:name).case_insensitive.scoped_to(:user) }
  end

但是这个测试失败了,因为:

Failure/Error: it { should validate_uniqueness_of(:name).scoped_to(:user).case_insensitive }

       Item did not properly validate that :name is case-insensitively
       unique within the scope of :user.
         After taking the given Item, setting its :name to ‹"an
         arbitrary value"›, and saving it as the existing record, then making a
         new Item and setting its :name to a different value, ‹"AN
         ARBITRARY VALUE"› and its :user to a different value, ‹nil›, the
         matcher expected the new Item to be invalid, but it was valid
         instead.

然而,这是我想要的行为(除了 Shoulda 为用户选择 nil 的奇怪部分)。当用户不同时,相同的名称应该是有效的。

可能是我没有正确使用范围测试,或者这对 Shoulda 来说是不可能的,这里是 the description of scoped tests。在这种情况下,您将如何编写模型测试来测试这种行为?

解决方法是three-fold:

  1. 范围为 :user_id 而不是模型中的 :user

  2. Re-write 对模型的验证将所有唯一性要求作为哈希的一部分包含在内

  3. 将测试范围扩大到 :user_id

问题中的代码可以正确检查唯一性 case-insensitively,但最好将所有唯一性要求都包含为散列,因为 the docs 中的示例采用这种形式即使对于单个声明(另外,这是我能找到的使 Shoulda 测试以正确的行为通过的唯一方法)。

这是工作代码的样子:

型号

class Item < ApplicationRecord
  belongs_to :user
  validates :name, uniqueness: { scope: :user_id, case_sensitive: false }
end

测试

RSpec.describe Item, type: :model do
  describe 'validations' do
    it { should validate_uniqueness_of(:name).scoped_to(:user_id).case_insensitive }
  end
end

我用 enum

试过了

型号

  validates(:plan_type,
            uniqueness: { scope: :benefit_class_id, case_sensitive: false })

      enum plan_type: {
        rrsp: 0,
        dpsp: 1,
        tfsa: 2,
        nrsp: 3,
        rpp: 4,
      }

测试

  it { should validate_uniqueness_of(:plan_type).scoped_to(:benefit_class_id).case_insensitive }

但总是出现类型错误(即 enum 值在测试中被大写)

  1) BenefitClass::RetirementPlan validations should validate that :plan_type is case-insensitively unique within the scope of :benefit_class_id
     Failure/Error:
       is_expected.to validate_uniqueness_of(:plan_type)
         .scoped_to(:benefit_class_id).case_insensitive

     ArgumentError:
       'RPP' is not a valid plan_type

但我能够编写一个通过的显式测试。

it 'validates uniqueness of plan_type scoped to benefit_class_id' do
  rp1 = FactoryBot.create(:retirement_plan)
  rp2 = FactoryBot.build(
                         :retirement_plan,
                         benefit_class_id: rp1.benefit_class_id,
                         plan_type: rp1.plan_type,
                         )
  expect(rp2).to_not be_valid
end