[英]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.