Używam Ruby on Rails 4 i rspec-rails gem 2.14. W przypadku mojego obiektu chciałbym porównać bieżący czas z updated_at
atrybutem obiektu po uruchomieniu akcji kontrolera, ale mam kłopoty, ponieważ specyfikacja nie spełnia wymagań. To znaczy, biorąc pod uwagę poniższy kod specyfikacji:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at).to eq(Time.now)
end
Po uruchomieniu powyższej specyfikacji pojawia się następujący błąd:
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 ==)
Jak mogę sprawić, by specyfikacja przeszła?
Uwaga : próbowałem również następujących (zwróć uwagę na utc
dodatek):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
ale specyfikacja nadal nie spełnia warunków (zwróć uwagę na różnicę wartości „otrzymano”):
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
źródło
źródło
===
, ale może to ucierpieć z powodu przekroczenia drugich granic. Prawdopodobnie najlepiej jest znaleźć lub napisać własny matcher, w którym przeliczasz na sekundy epoki i dopuszczasz niewielką absolutną różnicę.===
zamiast==
- obecnie porównujesz object_id dwóch różnych obiektów Time. Chociaż Timecop nie zamraża czasu serwera bazy danych. . . więc jeśli twoje znaczniki czasu są generowane przez RDBMS, to nie zadziała (spodziewam się, że nie stanowi to dla ciebie problemu)Odpowiedzi:
Obiekt Ruby Time zachowuje większą precyzję niż baza danych. Gdy wartość jest odczytywana z bazy danych, jest zachowywana tylko z dokładnością do mikrosekund, podczas gdy reprezentacja w pamięci jest dokładna do nanosekund.
Jeśli nie obchodzi Cię różnica w milisekundach, możesz wykonać to_s / to_i po obu stronach swoich oczekiwań
lub
Zapoznaj się z tym, aby uzyskać więcej informacji o tym, dlaczego czasy są różne
źródło
Timecop
perełki. Czy powinien po prostu rozwiązać problem poprzez „zamrożenie” czasu?be_within
właściweexpect {FooJob.perform_now}.to have_enqueued_job(FooJob).at(some_time)
Nie jestem pewien, czy.at
dopasowujący zaakceptowałby czas przekonwertowany na liczbę całkowitą, gdy.to_i
ktoś napotkał ten problem?Uważam, że używanie
be_within
domyślnego matchera rspec jest bardziej eleganckie:źródło
to_s
. Również,to_i
ato_s
może nie rzadko, jeśli czas jest blisko końca sekundy.be_within
dopasowanie zostało dodane do RSpec 2.1.0 7 listopada 2010 roku i od tego czasu kilkakrotnie ulepszane. rspec.info/documentation/2.14/rspec-expectations/…undefined method 'second' for 1:Fixnum
. Czy jest coś, czego potrzebujęrequire
?.second
to rozszerzenie rails: api.rubyonrails.org/classes/Numeric.htmltak, ponieważ
Oin
sugeruje, żebe_within
dopasowywanie jest najlepszą praktyką... i ma więcej zastosowań -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
Ale jeszcze jednym sposobem, jak sobie z tym poradzić, jest użycie wbudowanych Railsów
midday
imiddnight
atrybutów.Teraz to tylko do demonstracji!
Nie użyłbym tego w kontrolerze, ponieważ odrzucasz wszystkie połączenia Time.new => wszystkie atrybuty czasu będą miały ten sam czas => mogą nie udowodnić koncepcji, którą próbujesz osiągnąć. Zwykle używam go w komponowanych obiektach Ruby podobnych do tego:
Ale szczerze polecam trzymać się
be_within
dopasowania nawet przy porównywaniu Time.now.midday!Więc tak, proszę trzymać się
be_within
dopasowywania;)aktualizacja 2017-02
Pytanie w komentarzu:
możesz przekazać dowolne dopasowanie RSpec do
match
dopasowującego (więc np. możesz nawet wykonać testy API z czystym RSpec )Jeśli chodzi o „czasy post-db”, myślę, że masz na myśli ciąg, który jest generowany po zapisaniu do DB. Proponuję oddzielić ten przypadek od 2 oczekiwań (jedno zapewniające strukturę skrótu, drugie sprawdzające czas), więc możesz zrobić coś takiego:
Ale jeśli ten przypadek jest zbyt często w twoim zestawie testów, sugerowałbym napisanie własnego dopasowania RSpec (np.
be_near_time_now_db_string
) Przekonwertowania czasu ciągu db na obiekt Time, a następnie użycie tego jako częścimatch(hash)
:źródło
expect(hash_1).to eq(hash_2)
pracę, gdy niektóre wartości hash_1 są przed czasami db, a odpowiadające im wartości w hash_2 są post-db-times?Stary post, ale mam nadzieję, że pomoże to każdemu, kto wejdzie tutaj w poszukiwaniu rozwiązania. Myślę, że łatwiej i pewniej jest po prostu utworzyć datę ręcznie:
Gwarantuje to, że przechowywana data jest właściwa, bez robienia
to_x
lub martwienia się o wartości dziesiętne.źródło
Możesz przekonwertować obiekt daty / daty / godziny / godziny na ciąg, który jest przechowywany w bazie danych za pomocą
to_s(:db)
.źródło
Najłatwiejszym sposobem obejścia tego problemu jest utworzenie
current_time
metody pomocniczej testu, takiej jak:Teraz czas jest zawsze zaokrąglany do najbliższej milisekundy, aby porównania były proste:
źródło
.change(usec: 0)
jest bardzo pomocny.change(usec: 0)
sztuczki, aby rozwiązać problem bez korzystania z pomocnika specyfikacji. Jeśli pierwsza linia toTimecop.freeze(Time.current.change(usec: 0))
, możemy po prostu porównać.to eq(Time.now)
na końcu.Ponieważ porównywałem hashe, większość tych rozwiązań nie działała dla mnie, więc stwierdziłem, że najłatwiejszym rozwiązaniem jest po prostu pobranie danych z hasha, który porównywałem. Ponieważ czasy updated_at nie są dla mnie przydatne do testowania, działa to dobrze.
źródło