私は次のバリデーターを持っています:
# Source: http://guides.rubyonrails.org/active_record_validations_callbacks.html#custom-validators
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
object.errors[attribute] << (options[:message] || "is not formatted properly")
end
end
end
これを私のlibディレクトリ内のRSpecでテストできるようにしたいと思います。これまでのところ、EachValidator
を初期化する方法がわかりません。
これは、そのファイル用にノックアップしたクイックスペックで、うまく動作します。スタビングはおそらくクリーンアップできると思いますが、うまくいけばこれであなたが始めるのに十分でしょう。
require 'spec_helper'
describe "EmailValidator" do
before(:each) do
@validator = EmailValidator.new({:attributes => {}})
@mock = mock('model')
@mock.stub("errors").and_return([])
@mock.errors.stub('[]').and_return({})
@mock.errors[].stub('<<')
end
it "should validate valid address" do
@mock.should_not_receive('errors')
@validator.validate_each(@mock, "email", "[email protected]")
end
it "should validate invalid address" do
@mock.errors[].should_receive('<<')
@validator.validate_each(@mock, "email", "notvalid")
end
end
私は他のアプローチの大ファンではありません。テストが実装に近すぎるためです。また、それを理解するのはかなり難しいです。これは私が最終的に使用するアプローチです。これは私のバリデーターが実際に行ったことの全体的な単純化であることを覚えておいてください...単にもっと簡単にそれを実証したかっただけです。間違いなく最適化を行う必要があります
class OmniauthValidator < ActiveModel::Validator
def validate(record)
if !record.omniauth_provider.nil? && !%w(facebook github).include?(record.omniauth_provider)
record.errors[:omniauth_provider] << 'Invalid omniauth provider'
end
end
end
関連スペック:
require 'spec_helper'
class Validatable
include ActiveModel::Validations
validates_with OmniauthValidator
attr_accessor :omniauth_provider
end
describe OmniauthValidator do
subject { Validatable.new }
context 'without provider' do
it 'is valid' do
expect(subject).to be_valid
end
end
context 'with valid provider' do
it 'is valid' do
subject.stubs(omniauth_provider: 'facebook')
expect(subject).to be_valid
end
end
context 'with unused provider' do
it 'is invalid' do
subject.stubs(omniauth_provider: 'Twitter')
expect(subject).not_to be_valid
expect(subject).to have(1).error_on(:omniauth_provider)
end
end
end
基本的に私のアプローチは、実装の各部分に期待するのではなく、実際にその結果をテストできるように、偽のオブジェクト「Validatable」を作成することです
次のようなテスト目的で匿名クラスを作成することをお勧めします。
require 'spec_helper'
require 'active_model'
require 'email_validator'
RSpec.describe EmailValidator do
subject do
Class.new do
include ActiveModel::Validations
attr_accessor :email
validates :email, email: true
end.new
end
describe 'empty email addresses' do
['', nil].each do |email_address|
describe "when email address is #{email_address}" do
it "does not add an error" do
subject.email = email_address
subject.validate
expect(subject.errors[:email]).not_to include 'is not a valid email address'
end
end
end
end
describe 'invalid email addresses' do
['nope', '@', '[email protected].', '.', ' '].each do |email_address|
describe "when email address is #{email_address}" do
it "adds an error" do
subject.email = email_address
subject.validate
expect(subject.errors[:email]).to include 'is not a valid email address'
end
end
end
end
describe 'valid email addresses' do
['[email protected]', '[email protected]'].each do |email_address|
describe "when email address is #{email_address}" do
it "does not add an error" do
subject.email = email_address
subject.validate
expect(subject.errors[:email]).not_to include 'is not a valid email address'
end
end
end
end
end
これにより、複数の仕様で参照される可能性のあるValidatable
などのハードコードされたクラスが回避され、無関係に検証しようとする無関係な検証間の相互作用が原因で予期しない、デバッグが困難な動作が発生します。
Nealsの優れた例を基礎として使用して、次のことを思いつきました(RailsおよびRSpec 3の場合)。
# /spec/lib/slug_validator_spec.rb
require 'Rails_helper'
class Validatable
include ActiveModel::Model
include ActiveModel::Validations
attr_accessor :slug
validates :slug, slug: true
end
RSpec.describe SlugValidator do
subject { Validatable.new(slug: slug) }
context 'when the slug is valid' do
let(:slug) { 'valid' }
it { is_expected.to be_valid }
end
context 'when the slug is less than the minimum allowable length' do
let(:slug) { 'v' }
it { is_expected.to_not be_valid }
end
context 'when the slug is greater than the maximum allowable length' do
let(:slug) { 'v' * 64 }
it { is_expected.to_not be_valid }
end
context 'when the slug contains invalid characters' do
let(:slug) { '*' }
it { is_expected.to_not be_valid }
end
context 'when the slug is a reserved Word' do
let(:slug) { 'blog' }
it { is_expected.to_not be_valid }
end
end
仕様に新しいクラスを作成する代わりにオブジェクトを拡張するもう1つの例。 BitcoinAddressValidatorは、ここではカスタムバリデーターです。
require 'Rails_helper'
module BitcoinAddressTest
def self.extended(parent)
class << parent
include ActiveModel::Validations
attr_accessor :address
validates :address, bitcoin_address: true
end
end
end
describe BitcoinAddressValidator do
subject(:model) { Object.new.extend(BitcoinAddressTest) }
it 'has invalid bitcoin address' do
model.address = 'invalid-bitcoin-address'
expect(model.valid?).to be_falsey
expect(model.errors[:address].size).to eq(1)
end
# ...
end