ruby-on-rails ruby time rspec comparison

ruby on rails - Problemas para comparar el tiempo con RSpec



ruby-on-rails time (6)

El objeto Ruby Time mantiene una mayor precisión que la base de datos. Cuando el valor se lee desde la base de datos, solo se conserva a una precisión de microsegundos, mientras que la representación en memoria es precisa a nanosegundos.

Si no te importa la diferencia de milisegundos, podrías hacer un to_s / to_i en ambos lados de tu expectativa

expect(@article.updated_at.utc.to_s).to eq(Time.now.to_s)

o

expect(@article.updated_at.utc.to_i).to eq(Time.now.to_i)

Consulte this para obtener más información sobre por qué los tiempos son diferentes

Estoy usando Ruby on Rails 4 y la gema rspec-rails 2.14. Para mi objetivo, me gustaría comparar la hora actual con el updated_at objeto updated_at después de ejecutar una acción de controlador, pero estoy en problemas ya que la especificación no pasa. Es decir, dado el 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, aparece 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 hacer que la especificación pase?

Nota : Intenté también lo siguiente (tenga en cuenta la adición utc ):

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 ==)


La manera más fácil que encontré alrededor de este problema es crear un método de prueba de tiempo current_time como este:

module SpecHelpers # Database time rounds to the nearest millisecond, so for comparison its # easiest to use this method instead def current_time Time.zone.now.change(usec: 0) end end RSpec.configure do |config| config.include SpecHelpers end

Ahora el tiempo siempre se redondea al milisegundo más cercano para que las comparaciones sean sencillas:

it "updates updated_at attribute" do Timecop.freeze(current_time) patch :update @article.reload expect(@article.updated_at).to eq(current_time) end


Me parece más elegante usar el be_within rspec matcher predeterminado:

expect(@article.updated_at.utc).to be_within(1.second).of Time.now


Publicación anterior, pero espero que ayude a cualquiera que ingrese aquí para encontrar una solución. Creo que es más fácil y más confiable crear la fecha manualmente:

it "updates updated_at attribute" do freezed_time = Time.utc(2015, 1, 1, 12, 0, 0) #Put here any time you want Timecop.freeze(freezed_time) patch :update @article.reload expect(@article.updated_at).to eq(freezed_time) end

Esto asegura que la fecha almacenada es la correcta, sin hacer to_x ni preocuparse por los decimales.


Puede convertir el objeto date / datetime / time en una cadena, ya que está almacenado en la base de datos con to_s(:db) .

expect(@article.updated_at.to_s(:db)).to eq ''2015-01-01 00:00:00'' expect(@article.updated_at.to_s(:db)).to eq Time.current.to_s(:db)


sí, como Oin está sugiriendo be_within matcher es la mejor práctica

... y tiene más uscases -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher

Pero una forma más de cómo lidiar con esto es usar Rails incorporados en los atributos de midday y middnight .

it do # ... stubtime = Time.now.midday expect(Time).to receive(:now).and_return(stubtime) patch :update expect(@article.reload.updated_at).to eq(stubtime) # ... end

¡Ahora esto es solo para demostración!

No usaría esto en un controlador ya que está anulando todo el tiempo. Nuevas llamadas => todos los atributos de tiempo tendrán el mismo tiempo => puede que no prueben el concepto que intentan alcanzar. Normalmente lo uso en objetos de Ruby compuestos similares a esto:

class MyService attr_reader :time_evaluator, resource def initialize(resource:, time_evaluator: ->{Time.now}) @time_evaluator = time_evaluator @resource = resource end def call # do some complex logic resource.published_at = time_evaluator.call end end require ''rspec'' require ''active_support/time'' require ''ostruct'' RSpec.describe MyService do let(:service) { described_class.new(resource: resource, time_evaluator: -> { Time.now.midday } ) } let(:resource) { OpenStruct.new } it do service.call expect(resource.published_at).to eq(Time.now.midday) end end

Pero, sinceramente, recomiendo seguir con be_within matcher incluso comparando Time.now.midday.

Así que sí, los pls se quedan con be_within matcher;)

actualización 2017-02

Pregunta en comentario:

¿Qué pasa si los tiempos están en un Hash? ¿Hay alguna forma de que funcione (hash_1) .to eq (hash_2) cuando algunos valores hash_1 son pre-db-times y los valores correspondientes en hash_2 son post-db-times? -

expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `

puede pasar cualquier matcher RSpec al match coincidencia (por ejemplo, incluso puede hacer pruebas API con RSpec puro )

En cuanto a "post-db-times", supongo que te refieres a una cadena que se genera después de guardarla en DB. Yo sugeriría desvincular este caso a 2 expectativas (una asegurando la estructura de hash, la segunda revisando el tiempo). Así que puedes hacer algo como:

hash = {mytime: Time.now.to_s(:db)} expect(hash).to match({mytime: be_kind_of(String)) expect(Time.parse(hash.fetch(:mytime))).to be_within(3.seconds).of(Time.now)

Pero si este caso es muy frecuente en su suite de pruebas, le sugiero que escriba su propio repetidor RSpec (por ejemplo, be_near_time_now_db_string ) convirtiendo el tiempo de serie de db en objeto Time y luego use esto como parte de la match(hash) :

expect(hash).to match({mytime: be_near_time_now_db_string}) # you need to write your own matcher for this to work.