Estoy usando Ruby on Rails 4 y la gema rspec-rails 2.14. Para mi objeto, me gustaría comparar la hora actual con el updated_at
atributo del objeto después de ejecutar una acción del controlador, pero estoy en problemas porque la especificación no pasa. Es decir, dado lo siguiente es el código de especificación:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at).to eq(Time.now)
end
Cuando ejecuto la especificación anterior, obtengo el siguiente error:
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 ==)
¿Cómo puedo aprobar la especificación?
Nota : probé también lo siguiente (tenga en cuenta la utc
adición):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
pero la especificación aún no pasa (tenga en cuenta la diferencia de valor "obtenido"):
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
fuente
fuente
===
, pero eso puede sufrir al cruzar segundos límites. Probablemente lo mejor sea encontrar o escribir su propio comparador, en el que convierta a segundos de época y permita una pequeña diferencia absoluta.===
lugar de==
- actualmente está comparando el object_id de dos objetos Time diferentes. Aunque Timecop no congelará el tiempo del servidor de la base de datos. . . así que si sus marcas de tiempo las genera el RDBMS, no funcionaría (aunque espero que no sea un problema para usted)Respuestas:
El objeto Ruby Time mantiene una mayor precisión que la base de datos. Cuando se vuelve a leer el valor de la base de datos, solo se conserva con una precisión de microsegundos, mientras que la representación en memoria es precisa en nanosegundos.
Si no le importa la diferencia de milisegundos, puede hacer un to_s / to_i en ambos lados de su expectativa
o
Consulte esto para obtener más información sobre por qué los tiempos son diferentes.
fuente
Timecop
gema. ¿Debería resolver el problema simplemente "congelando" el tiempo?be_within
es la correctaexpect {FooJob.perform_now}.to have_enqueued_job(FooJob).at(some_time)
la.at
puesta en cola de trabajos. No estoy seguro de que el comparador acepte un tiempo convertido en entero con.to_i
alguien que haya enfrentado este problema.Encuentro usar el
be_within
matcher rspec predeterminado más elegante:fuente
to_s
. También,to_i
yto_s
puede fallar con poca frecuencia si el tiempo está cerca del final de un segundo.be_within
comparador se agregó a RSpec 2.1.0 el 7 de noviembre de 2010 y se mejoró varias veces desde entonces. rspec.info/documentation/2.14/rspec-expectations/…undefined method 'second' for 1:Fixnum
. ¿Hay algo que necesiterequire
?.second
es una extensión de rieles: api.rubyonrails.org/classes/Numeric.htmlsí, como
Oin
sugiere que elbe_within
matcher es la mejor práctica... y tiene algunos uscases más -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
Pero una forma más de cómo lidiar con esto es usar Rails integrados
midday
ymiddnight
atributos.¡Esto es solo para demostración!
No usaría esto en un controlador, ya que está eliminando todas las llamadas de Time.new => todos los atributos de tiempo tendrán el mismo tiempo => puede que no pruebe el concepto que está tratando de lograr. Normalmente lo uso en objetos de Ruby compuestos similares a este:
Pero honestamente, recomiendo seguir con el
be_within
matcher incluso al comparar Time.now.midday!Así que sí, quédate con el
be_within
matcher;)actualización 2017-02
Pregunta en comentario:
puede pasar cualquier comparador RSpec al
match
comparador (por ejemplo, puede incluso hacer pruebas de API con RSpec puro )En cuanto a "post-db-times", supongo que te refieres a una cadena que se genera después de guardar en DB. Sugeriría desacoplar este caso a 2 expectativas (una asegurando la estructura hash, la segunda verificando el tiempo) Para que pueda hacer algo como:
Pero si este caso se encuentra con demasiada frecuencia en su conjunto de pruebas, le sugiero que escriba su propio comparador RSpec (por ejemplo
be_near_time_now_db_string
) convirtiendo el tiempo de cadena db en un objeto Time y luego use esto como parte dematch(hash)
:fuente
expect(hash_1).to eq(hash_2)
funcione cuando algunos valores hash_1 son pre-db-times y los valores correspondientes en hash_2 son post-db-times?Publicación antigua, pero espero que ayude a cualquiera que entre aquí en busca de una solución. Creo que es más fácil y confiable crear la fecha manualmente:
Esto asegura que la fecha almacenada sea la correcta, sin tener que
to_x
preocuparse por los decimales.fuente
Puede convertir el objeto date / datetime / time en una cadena tal como está almacenado en la base de datos con
to_s(:db)
.fuente
La forma más fácil que encontré para solucionar este problema es crear un
current_time
método auxiliar de prueba como este:Ahora, el tiempo siempre se redondea al milisegundo más cercano para que las comparaciones sean sencillas:
fuente
.change(usec: 0)
es muy útil.change(usec: 0)
truco para resolver el problema sin usar también un asistente de especificaciones. Si la primera línea esTimecop.freeze(Time.current.change(usec: 0))
, simplemente podemos comparar.to eq(Time.now)
al final.Debido a que estaba comparando hashes, la mayoría de estas soluciones no me funcionaron, así que encontré que la solución más fácil era simplemente tomar los datos del hash que estaba comparando. Dado que updated_at veces no son realmente útiles para mí para probar, esto funciona bien.
fuente