2013-03-28 250 views
0
多個自定義校驗

我想了兩個自定義驗證運行規格:測試使用RSpec

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/時兩個規格都能通過。任何人都可以向我解釋發生了什麼?

更新: 二手探究性的提示打印出來的誤差散:

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類定義組合。預期這種行爲?如果我使用不同的類名,則兩個規格都會通過。

規格/驗證/ 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 

應用程序/驗證/ 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 

規格/驗證/ 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[[email protected] [email protected] [email protected] [email protected]] 
     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[[email protected],com user_at_foo.org [email protected]] 
     addresses.each do |invalid_address| 
     subject.email = invalid_address 
     subject.should be_invalid 
     end 
    end 
    end 
end 

應用程序/驗證/ 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 
     # [email protected] is excluded 
     # treetop must respond to domain 
     # We exclude valid email values like <[email protected]> 
     # 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的護欄2.11.0

回答

1

你的模型實例是無效的,但你不知道爲什麼。嘗試改變

subject.should be_valid 

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

現在失敗消息將打印出的誤差散。

另外提示:Don't rescue Exception

編輯

看樣子是Validatable類時這兩個規範是運行的定義組合。預期這種行爲?

是的,這對於Ruby類來說是正常的。當兩個spec文件都是必需的,每個Validatable類的主體被執行,所以你最終得到一個包含兩個驗證的類。

需要要麼通過使受試者通過驗證,是不是在測試,例如到驗證隔離:

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

或測試用於從所述驗證測試中的特定錯誤消息:

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

PS。認真 - 不要拯救例外。這沒什麼好的。

+0

感謝您的提示。看起來兩個'Validatable'類定義導致了這個問題。我已經更新了我的問題。 –

+0

感謝您的更新。我不希望規範互相依賴,所以我只是要給每個Validatable類一個獨特的名稱。我接受了你的建議,並刪除了救援例外。 –

0

我自己遇到這個問題,是的,你可以重命名類,但我使用的解決方案是創建和拆除你的規範中的Validatable類。

這裏有一個代碼片段:

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 

您將不得不從名稱空間中刪除常量,而不是從對象中刪除常量。