【问题标题】:How to correctly check uniqueness and scope with Shoulda如何使用 Shoulda 正确检查唯一性和范围
【发布时间】:2018-03-17 10:59:00
【问题描述】:

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

Item 模型当前设置为:

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.

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

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

【问题讨论】:

  • 唯一性匹配器不支持使用关联作为作用域,有some issues in Github。作为一种解决方法,您应该使用scoped_to(:user_id)。但是即使进行了这种更改,scope_tocase_insensitive 似乎也不起作用。我认为这是一个错误。 @oneWorkingHeadphone 你会举报吗?否则我会做的。
  • @ana06 很好,谢谢!我将打开一个新问题并参考您链接的问题。
  • 这个问题现在好像已经解决了

标签: rspec shoulda


【解决方案1】:

解决方法有三个:

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

  2. 在模型上重新编写验证,以将所有唯一性要求作为哈希的一部分包含在内

  3. 将测试范围限定为:user_id

问题中的代码将起作用,因为它可以不区分大小写地正确检查唯一性,但最好将所有唯一性要求包含为哈希,因为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

【讨论】:

    【解决方案2】:

    我用 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
    

    【讨论】:

    • 您的第二个测试与应该测试的条件不同。如果不区分大小写,应该检查 rppRPP。您的测试不检查第二种情况,它只是将其设置为相同的值。
    猜你喜欢
    • 1970-01-01
    • 2017-11-29
    • 1970-01-01
    • 2012-03-28
    • 1970-01-01
    • 2015-08-24
    • 2013-05-01
    • 2015-09-18
    • 1970-01-01
    相关资源
    最近更新 更多