Ich benutze Ruby on Rails 4 und das rspec-Rails Gem 2.14. Für ein mein Objekt möchte ich die aktuelle Zeit mit dem updated_at
Objektattribut nach einem Controller-Aktionslauf vergleichen, aber ich habe Probleme, da die Spezifikation nicht bestanden wird. Das heißt, wenn der folgende Spezifikationscode angegeben ist:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at).to eq(Time.now)
end
Wenn ich die obige Spezifikation ausführe, wird folgende Fehlermeldung angezeigt:
Failure/Error: expect(@article.updated_at).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: Thu, 05 Dec 2013 08:42:20 CST -06:00
(compared using ==)
Wie kann ich die Spezifikation bestehen lassen?
Hinweis : Ich habe auch Folgendes versucht (beachten Sie den utc
Zusatz):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
Die Spezifikation wird jedoch immer noch nicht bestanden (beachten Sie die "got" -Wertdifferenz):
Failure/Error: expect(@article.updated_at.utc).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: 2013-12-05 14:42:20 UTC
(compared using ==)
ruby-on-rails
ruby
time
rspec
comparison
Backo
quelle
quelle
===
, aber das kann unter dem Überschreiten der zweiten Grenzen leiden. Wahrscheinlich ist es am besten, einen eigenen Matcher zu finden oder zu schreiben, in dem Sie in Epochensekunden konvertieren und einen kleinen absoluten Unterschied berücksichtigen.===
anstelle von==
- Derzeit vergleichen Sie die object_id von zwei verschiedenen Zeitobjekten. Obwohl Timecop die Datenbankserverzeit nicht einfriert. . . Wenn Ihre Zeitstempel vom RDBMS generiert werden, funktioniert dies nicht (ich gehe davon aus, dass dies hier kein Problem für Sie ist)Antworten:
Das Ruby Time-Objekt ist präziser als die Datenbank. Wenn der Wert aus der Datenbank zurückgelesen wird, bleibt er nur mit einer Genauigkeit von Mikrosekunden erhalten, während die Darstellung im Speicher auf Nanosekunden genau ist.
Wenn Ihnen der Millisekundenunterschied egal ist, können Sie auf beiden Seiten Ihrer Erwartung ein to_s / to_i ausführen
oder
Siehe diese für weitere Informationen darüber , warum die Zeiten sind anders
quelle
Timecop
Edelstein. Sollte es das Problem nur lösen, indem es die Zeit "einfriert"?be_within
die richtigeexpect {FooJob.perform_now}.to have_enqueued_job(FooJob).at(some_time)
Ich bin nicht sicher, ob der.at
Matcher eine in Integer konvertierte Zeit akzeptieren würde, wenn.to_i
jemand mit diesem Problem konfrontiert ist.Ich finde die Verwendung des
be_within
Standard-Rspec-Matchers eleganter:quelle
to_s
. Auchto_i
undto_s
kann selten fehlschlagen, wenn die Zeit nahe dem Ende einer Sekunde ist.be_within
Matcher am 7. November 2010 zu RSpec 2.1.0 hinzugefügt und seitdem einige Male verbessert wurde. rspec.info/documentation/2.14/rspec-expectations/…undefined method 'second' for 1:Fixnum
. Muss ich etwas tunrequire
?.second
ist eine Rails-Erweiterung: api.rubyonrails.org/classes/Numeric.htmlJa, wie
Oin
es nahelegt, istbe_within
Matcher die beste Vorgehensweise... und es gibt noch einige weitere Fälle -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
Eine weitere Möglichkeit, damit umzugehen, besteht darin, integrierte Rails
midday
undmiddnight
Attribute zu verwenden.Dies ist nur zur Demonstration!
Ich würde dies nicht in einem Controller verwenden, da Sie alle Time.new-Aufrufe stubben => Alle Zeitattribute haben dieselbe Zeit => beweisen möglicherweise nicht das Konzept, das Sie erreichen möchten. Normalerweise verwende ich es in zusammengesetzten Ruby-Objekten, die folgendermaßen aussehen:
Aber ehrlich gesagt empfehle ich,
be_within
auch beim Vergleich von Time.now.midday bei Matcher zu bleiben!Also ja bitte bleib beim
be_within
Matcher;)Update 2017-02
Frage im Kommentar:
Sie können jeden RSpec-Matcher an den
match
Matcher übergeben (z. B. können Sie sogar API-Tests mit reinem RSpec durchführen ).Was "post-db-times" betrifft, meinen Sie wohl eine Zeichenfolge, die nach dem Speichern in der Datenbank generiert wird. Ich würde vorschlagen, diesen Fall an zwei Erwartungen zu entkoppeln (eine stellt die Hash-Struktur sicher, die zweite überprüft die Zeit). Sie können also Folgendes tun:
Wenn dieser Fall jedoch in Ihrer Testsuite zu häufig vorkommt, würde ich empfehlen, einen eigenen RSpec-Matcher zu schreiben (z. B.
be_near_time_now_db_string
), der die Zeit der Datenbankzeichenfolge in ein Time-Objekt konvertiert, und diesen dann als Teil des folgenden Elements zu verwendenmatch(hash)
:quelle
expect(hash_1).to eq(hash_2)
Gibt es eine Möglichkeit, Arbeit zu leisten , wenn einige Hash_1-Werte Pre-DB-Zeiten und die entsprechenden Werte in Hash_2 Post-DB-Zeiten sind?Alter Beitrag, aber ich hoffe, er hilft jedem, der hier eintritt, nach einer Lösung. Ich denke, es ist einfacher und zuverlässiger, das Datum einfach manuell zu erstellen:
Dies stellt sicher, dass das gespeicherte Datum das richtige ist, ohne dass Sie
to_x
sich um Dezimalstellen kümmern müssen.quelle
Sie können das Objekt Datum / Datum / Uhrzeit / Uhrzeit in eine Zeichenfolge konvertieren, wenn es in der Datenbank mit gespeichert ist
to_s(:db)
.quelle
Der einfachste Weg, um dieses Problem zu umgehen, besteht darin, eine
current_time
Testhilfemethode wie folgt zu erstellen :Jetzt wird die Zeit immer auf die nächste Millisekunde gerundet, um Vergleiche zu vereinfachen:
quelle
.change(usec: 0)
ist sehr hilfreich.change(usec: 0)
Trick verwenden, um das Problem zu lösen, ohne auch einen Spezifikationshelfer zu verwenden. Wenn die erste Zeile istTimecop.freeze(Time.current.change(usec: 0))
, können wir einfach.to eq(Time.now)
am Ende vergleichen .Da ich Hashes verglichen habe, haben die meisten dieser Lösungen bei mir nicht funktioniert. Daher war es für mich am einfachsten, einfach die Daten aus dem Hash zu holen, den ich verglichen habe. Da die aktualisierten_at-Zeiten für mich zum Testen nicht wirklich nützlich sind, funktioniert dies einwandfrei.
quelle