0

2 つのカスタム バリデータの仕様を実行しようとしています。

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両方の仕様が実行されると、クラス定義が結合されているようです。この動作は予期されたものですか? 個別のクラス名を使用すると、両方の仕様に合格します。

仕様/バリデーター/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[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

アプリ/バリデーター/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 の使用

4

2 に答える 2

1

モデル インスタンスが無効ですが、理由がわかりません。変更してみる

subject.should be_valid

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

これで、失敗メッセージにエラー ハッシュが出力されます。

別のヒント: Exception をレスキューしないでください

編集

両方の仕様が実行されると、Validatable クラス定義が結合されているようです。この動作は予期されたものですか?

はい、Ruby クラスではそれが普通です。両方の仕様ファイルが必要な場合、各Validatableクラス本体が実行されるため、両方の検証を含むクラスになります。

サブジェクトがテスト対象ではない検証に合格するようにすることで、検証を分離する必要があります。

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

または、テスト中の検証からの特定のエラー メッセージのテスト:

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

PS。真剣に - 例外を救出しないでください。それでは良いことは何も起こりません。

于 2013-03-29T01:49:30.053 に答える
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

オブジェクトではなく、その名前空間から定数を削除する必要があります。

于 2014-03-03T16:03:46.713 に答える