【问题标题】:Testing Multiple Custom Validators with RSpec使用 RSpec 测试多个自定义验证器
【发布时间】:2013-03-28 23:47:09
【问题描述】:

我正在尝试为两个自定义验证器运行规范:

spec/validators/email_validator_spec.rb
spec/validators/phone_validator_spec.rb

当我运行 bundle exec rspec spec/validators/ 时,phone_validator_spec.rb 规范失败:

1) PhoneValidator with a valid phone number should be valid
     Failure/Error: subject.should be_valid
       expected valid? to return true, got false
     # ./spec/validators/phone_validator_spec.rb:20:in `block (4 levels) in <top (required)>'
     # ./spec/validators/phone_validator_spec.rb:18:in `each'
     # ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'

但是,当我使用命令 bundle exec rspec spec/validators/phone_validator_spec.rb 单独运行该规范时,它通过了。

当我删除email_validator_spec.rb 然后phone_validator_spec.rb 使用命令bundle exec rspec spec/validators/ 传递。

当我运行bundle exec rspec spec/validators/ 时,我希望这两个规范都能通过。谁能向我解释发生了什么?

更新: 使用 zetetic 的提示打印出错误哈希:

1) PhoneValidator with a valid phone number should be valid
     Failure/Error: subject.errors.should == {}
       expected: {}
            got: #<ActiveModel::Errors:0x37b2460 @base=#<Validatable:0x37b2700 @validation_context=nil, @errors=#<ActiveModel::Errors:0x37b2460 ...>, @phone_number="1112223333">, @messages={:email=>["is invalid"]}> (using ==)
       Diff:
       @@ -1 +1,8 @@
       +#<ActiveModel::Errors:0x37b2460
       + @base=
       +  #<Validatable:0x37b2700
       +   @errors=#<ActiveModel::Errors:0x37b2460 ...>,
       +   @phone_number="1112223333",
       +   @validation_context=nil>,
       + @messages={:email=>["is invalid"]}>
     # ./spec/validators/phone_validator_spec.rb:21:in `block (4 levels) in <top (required)>'
     # ./spec/validators/phone_validator_spec.rb:18:in `each'
     # ./spec/validators/phone_validator_spec.rb:18:in `block (3 levels) in <top (required)>'

似乎Validatable 类定义在运行两个规范时合并。这种行为是预期的吗?如果我使用不同的类名,两个规范都会通过。

spec/validators/phone_validator_spec.rb

require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/phone_validator')

class Validatable
  include ActiveModel::Validations
  attr_accessor :phone_number
  validates :phone_number, phone: true
end

describe PhoneValidator do

  subject { Validatable.new }

  describe "with a valid phone number" do
    it "should be valid" do
      phone_numbers = ["1112223333", "123222ABCD"]
      phone_numbers.each do |phone_number|
        subject.phone_number = phone_number
        subject.should be_valid
      end
    end 
  end
end

app/validators/phone_validator.rb

class PhoneValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    return if value.blank?
    unless value =~ /^[A-Za-z0-9]{10}$/
      object.errors[attribute] << (options[:message] || "is not formatted properly")
    end
  end
end

spec/validators/email_validator_spec.rb

require 'active_model'
require 'rspec/rails/extensions'
require File.expand_path('app/validators/email_validator')

class Validatable
  include ActiveModel::Validations
  attr_accessor :email
  validates :email, email: true
end

describe EmailValidator do

  subject { Validatable.new }

  describe "with a valid email address" do
    it "should be valid" do
      addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
      addresses.each do |valid_address|
        subject.email = valid_address
        subject.should be_valid
      end
    end
  end

  describe "with an invalid phone number" do
    it "should be invalid" do
      addresses = %w[user@foo,com user_at_foo.org example.user@foo]
      addresses.each do |invalid_address|
        subject.email = invalid_address
        subject.should be_invalid
      end
    end
  end
end

app/validators/email_validator.rb

require 'mail'

class EmailValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    begin
      m = Mail::Address.new(value)
      # We must check that value contains a domain and that value is an email address
      r = m.domain && m.address == value
      t = m.__send__(:tree)
      # We need to dig into treetop
      # A valid domain must have dot_atom_text elements size > 1
      # user@localhost is excluded
      # treetop must respond to domain
      # We exclude valid email values like <user@localhost.com>
      # Hence we use m.__send__(tree).domain
      r &&= (t.domain.dot_atom_text.elements.size > 1)
    rescue => e   
      r = false
    end
    object.errors[attribute] << (options[:message] || "is invalid") unless r
  end
end

使用 rails 3.2.11、rspec-rails 2.11.0

【问题讨论】:

    标签: ruby-on-rails-3 validation rspec


    【解决方案1】:

    您的模型实例无效,但您不知道原因。尝试改变

    subject.should be_valid
    

    subject.valid?
    subject.errors.should == {}
    

    现在失败消息将打印出错误哈希。

    另一个提示:Don't rescue Exception

    编辑

    当两个规范都运行时,Validatable 类定义似乎被合并了。这是预期的行为吗?

    是的,这对于 Ruby 类是正常​​的。当两个规范文件都需要时,每个 Validatable 类主体都会被执行,因此您最终会得到一个包含两个验证的类。

    您需要通过使受试者通过未测试的验证来隔离验证,例如:

    subject { Validatable.new(:email => "some value") }
    

    或测试来自被测验证的特定错误消息:

    subject.valid?
    subject.errors(:email).should include("is invalid")
    

    附言。说真的——不要拯救异常。这样做不会有什么好处。

    【讨论】:

    • 感谢您的提示。似乎是两个Validatable 类定义导致了问题。我已经更新了我的问题。
    • 感谢您的更新。我不希望规范相互依赖,所以我只想给每个 Validatable 类一个唯一的名称。我也采纳了你的建议并删除了救援异常。
    【解决方案2】:

    我自己也遇到了这个问题,是的,您可以重命名该类,但我使用的解决方案是在您的规范中创建和拆除 Validatable 类。

    这是一个代码sn-p:

    describe "HttpUriValidator",
      "Custom validator to ensure URL is a valid URI." do
    
      # Create the dummy class once when the test is run.
      before(:all) do
        class Validatable
          include ActiveModel::Validations
          attr_accessor  :url
          validates :url, http_uri: true
        end
      end
      # Must tearing down the class or it will taint other tests using its
      # name.
      after(:all) { Object.send(:remove_const, :Validatable) }
    
      subject { Validatable.new }
    

    编辑::

    当您声明一个包装您的测试类的模块时请注意(以避免在测试中对其他类进行命名空间),即。

    module Foo::Bar
      describe Something do
        after(:all) { Foo::Bar.send(:remove_const, :Testable) }
      end
    end
    

    您必须从该命名空间中删除常量,而不是 Object。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      相关资源
      最近更新 更多