Factory-Girl erstellen, das meine Modellvalidierung umgeht

77

Ich verwende Factory Girl, um zwei Instanzen in meinem Modell- / Komponententest für eine Gruppe zu erstellen. Ich teste das Modell, um zu überprüfen, ob ein Aufruf von .current nur die 'aktuellen' Gruppen gemäß dem unten angegebenen Ablaufattribut zurückgibt ...

  describe ".current" do
    let!(:current_group) { FactoryGirl.create(:group, :expiry => Time.now + 1.week) }
    let!(:expired_group) { FactoryGirl.create(:group, :expiry => Time.now - 3.days) }

    specify { Group.current.should == [current_group] }
  end

Mein Problem ist, dass ich eine Validierung im Modell habe, die überprüft, ob der Ablauf einer neuen Gruppe nach dem heutigen Datum liegt. Dies löst den folgenden Validierungsfehler aus.

  1) Group.current 
     Failure/Error: let!(:expired_group) { FactoryGirl.create(:group, :expiry => Time.now - 3.days) }
     ActiveRecord::RecordInvalid:
       Validation failed: Expiry is before todays date

Gibt es eine Möglichkeit, die Gruppe zwangsweise zu erstellen oder die Validierung zu umgehen, wenn Sie mit Factory Girl erstellen?

Norto23
quelle

Antworten:

91

Dies ist nicht sehr spezifisch für FactoryGirl, aber Sie können die Validierungen beim Speichern von Modellen jederzeit umgehen über save(:validate => false):

describe ".current" do
  let!(:current_group) { FactoryGirl.create(:group) }
  let!(:old_group) {
    g = FactoryGirl.build(:group, :expiry => Time.now - 3.days)
    g.save(:validate => false)
    g
  }

  specify { Group.current.should == [current_group] }
end
Brandan
quelle
Eine bessere Lösung finden Sie in der Antwort von Jason Denney unten.
David Hempy
1
seit 1.9.1 können Sie tung.tap { |g| g.save(validate: false) }
yefrem
58

Ich bevorzuge diese Lösung von https://github.com/thoughtbot/factory_girl/issues/578 .

In der Fabrik:

to_create {|instance| instance.save(validate: false) }

BEARBEITEN:

Wie im Thread, auf den verwiesen wird, und in den Kommentaren / Lösungen anderer erwähnt, möchten Sie dies wahrscheinlich in einen Merkmalsblock einschließen, um Verwirrung / Probleme an anderer Stelle in Ihren Tests zu vermeiden. Zum Beispiel, wenn Sie Ihre Validierungen testen.

Jason Denney
quelle
5
Dies ist eine viel elegantere Lösung als die akzeptierte.
Kyle Heironimus
5
Denken Sie daran, dass Sie, wenn Sie dies für Ihre Allzweckfabrik tun, JEDES Mal, wenn Sie diese Fabrik erstellt haben, Validierungen überspringen. Es ist wahrscheinlich am besten, diese Technik nur in einer Unterfabrik (oder in einem Merkmal) anzuwenden.
tgf
Sie werden dies mit ziemlicher Sicherheit in eine Eigenschaft umwandeln wollen. Siehe die Antwort von Tim Scott unten.
David Hempy
40

Es ist eine schlechte Idee, Validierungen standardmäßig im Werk zu überspringen. Einige Haare werden herausgezogen, um das zu finden.

Der schönste Weg, denke ich:

trait :skip_validate do
  to_create {|instance| instance.save(validate: false)}
end

Dann in Ihrem Test:

create(:group, :skip_validate, expiry: Time.now + 1.week)
Tim Scott
quelle
1
Dies ist der beste Weg, um dieses Problem zu lösen!
Hatenine
Gibt es eine Möglichkeit, dies auf alle Fabriken anzuwenden?
Adam
Es ist immer wieder erstaunlich, diese kleinen Schnipsel in bereits vorhandenen Codebasen zu finden. Sie wissen, dass ein aktueller oder ehemaliger Mitarbeiter diese genaue Antwort bereits gefunden hat.
Dylan Pierce
7

Für diesen speziellen Fall der Datumsüberprüfung können Sie auch das Timecop- Juwel verwenden, um die Zeit vorübergehend zu ändern und den alten Datensatz zu simulieren, der in der Vergangenheit erstellt wurde.

Gabe Martin-Dempesy
quelle
7
foo = build(:foo).tap{ |u| u.save(validate: false) }
Chris Habgood
quelle
6

Es ist nicht am besten, die gesamte Validierung dieses Modells zu überspringen.

spec/factories/traits.rbDatei erstellen .

FactoryBot.define do
  trait :skip_validate do
    to_create { |instance| instance.save(validate: false) }
  end
end

fix spec

describe ".current" do
  let!(:current_group) { FactoryGirl.create(:group, :skip_validate, :expiry => Time.now + 1.week) }
  let!(:expired_group) { FactoryGirl.create(:group, :skip_validate, :expiry => Time.now - 3.days) }

  specify { Group.current.should == [current_group] }
end
HAZI
quelle
1

Abhängig von Ihrem Szenario können Sie die Validierung so ändern, dass sie nur beim Update erfolgt. Beispiel::validates :expire_date, :presence => true, :on => [:update ]

JoaoHornburg
quelle
1

Ihre Fabriken sollten standardmäßig gültige Objekte erstellen. Ich fand heraus, dass transiente Attribute verwendet werden können, um bedingte Logik wie folgt hinzuzufügen:

transient do
  skip_validations false
end

before :create do |instance, evaluator|
  instance.save(validate: false) if evaluator.skip_validations
end

In Ihrem Test:

create(:group, skip_validations: true)
Acamino
quelle
0

Oder Sie können beide FactoryBotund Timecopmit so etwas wie:

trait :expired do
  transient do
    travel_backward_to { 2.days.ago }
  end
  before(:create) do |_instance, evaluator|
    Timecop.travel(evaluator.travel_backward_to)
  end
  after(:create) do
    Timecop.return
  end
end

let!(:expired_group) { FactoryGirl.create(:group, :expired, travel_backward_to: 5.days.ago, expiry: Time.now - 3.days) }

Bearbeiten : Aktualisieren Sie dieses Ereignis nicht, nachdem die Erstellung oder Validierung fehlgeschlagen ist.

brcebn
quelle