繁体   English   中英

Rails 5.2 rspec - 如何测试 model 是否实际使用自定义验证器?

[英]Rails 5.2 rspec - How to test if a model is actually using a custom validator?

我创建了一个自定义验证器,它有自己的特定单元测试来检查它是否有效。

使用 should-matchers 有一个添加validates_with匹配器的建议,所以你可以写:

subject.validates_with(:custom_validator)

这个建议被拒绝是正确的,因为它并没有真正测试 model 的行为。

但是我的 model 有 4 个使用自定义验证器的字段,我希望测试该行为 - 即正在验证这 4 个字段,就像我正在测试它们是否存在一样:

describe '#attribute_name' do
  it { is_expected.to validate_presence_of(:attribute_name) }
end

那么我怎样才能编写一个基本上做同样事情的测试,有点像这样:

describe '#attribute_name' do
  it { is_expected.to use_custom_validator_on(:attribute_name) }
end

这个问题问同样的事情,答案建议建立一个测试 model。但是,我的验证器需要一个选项,它是这样使用的:

\app\models\fund.rb

class Fund < ActiveRecord
  validates :ein, digits: { exactly: 9 }
end

因此,如果我构建一个测试 model,并按照建议进行测试:

it 'is has correct number of digits' do
  expect(build(:fund, ein: '123456789')).to be_valid
end

it 'is has incorrect number of digits' do
  expect(build(:fund, ein: '123').to be_invalid
end

我收到RecordInvalid错误(来自我自己的验证器。哈哈)说我没有为验证器提供所需的选项。 该选项称为“完全”。

1) Fund#ein validates digits
     Failure/Error: raise ActiveRecord::RecordInvalid # option :exactly was not provided (incorrect usage)

     ActiveRecord::RecordInvalid:
       Record invalid

那么 Rspec 是不是“看到”了 model 文件中定义的值“9”?

显然,在测试中定义它是没有意义的,因为这是我要测试的定义行为。 把它想象成对{ length: x }选项的validates_length_of测试。

肯定有一种方法可以测试这个自定义验证器选项是否设置在 model 上?

验证器代码

class DigitsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?

    length = options[:exactly]
    regex = /\A(?!0{#{length}})\d{#{length}}\z/
    return unless value.scan(regex).empty?

    record.errors[attribute] << (options[:message] || error_msg(length))
  end

  private

  def error_msg(length)
    I18n.t('activerecord.errors.custom.digits_attr_invalid', length: length) if length
    raise ActiveRecord::RecordInvalid # option :exactly was not provided (incorrect usage)
  end
end

有趣的旁注

显然,如果我从DigitsValidator中删除“raise”行,那么两个测试都会成功。 我的代码有什么我看不到的问题吗?

我认为您不应以测试模型是否使用特定验证器为目标。 而是在特定情况下检查模型是否有效/无效。 换句话说,您应该能够在不了解实现的情况下测试模型的行为。

因此,在这种情况下,您应该使用验证器的“完全”选项正确设置模型,并测试模型验证是否足够。

另一方面,如果您担心将来有人会滥用验证器,并且“完全”是验证器的必需选项,那么每次不存在该选项时都应引发错误,并单独测试验证器就像这里解释的: 如何测试自定义验证器?

我认为您必须添加一个return语句,不是吗? :-)

 def error_msg(length)
   return I18n.t('activerecord.errors.custom.digits_attr_invalid', length: length) if length
   raise ActiveRecord::RecordInvalid # option :exactly was not provided (incorrect usage)
 end

或者,删除该方法并在设置length后使用防护装置:

  class DigitsValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
      return if value.blank?

      length = options[:exactly]
      raise ActiveRecord::RecordInvalid if length.nil?

      regex = /\A(?!0{#{length}})\d{#{length}}\z/
      return unless value.scan(regex).empty?

      record.errors[attribute] << 
        (options[:message] || 
          I18n.t('activerecord.errors.custom.digits_attr_invalid', length: length))
      end
    end

我喜欢不包括 model 上的测试的想法,这些测试假定知道自定义验证器正在验证的确切内容。 (否则,我们将在自定义验证器的测试和 model 的测试中重复逻辑。)

我通过使用Mocha (Ruby 的模拟库)来设置预期,即在我的 model 的正确对应字段上调用我的每个自定义验证器的validate_each方法来解决这个问题。简化示例:

Model class:

class User
    include ActiveModel::Model

    attr_accessor :first_name, :last_name

    validates :first_name, first_name: true
    validates :last_name, last_name: true
end

自定义验证器类:

class FirstNameValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
        # ...
    end
end

class LastNameValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
        # ...
    end
end

Model 测试 class:

class UserTest < ActiveSupport::TestCase
    def test_custom_validators_called_on_the_appropriate_fields
        user = User.new(first_name: 'Valued', last_name: 'Customer')

        FirstNameValidator.any_instance.expects(:validate_each).with(user, :first_name, 'Valued')
        LastNameValidator.any_instance.expects(:validate_each).with(user, :last_name, 'Customer')

        assert_predicate user, :valid?
    end
end

暂无
暂无

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

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