with tutorial tests test run rails how ruby-on-rails time rspec

ruby-on-rails - tutorial - rspec with rails



Rails Incoherencias de tiempo con rspec (10)

Estoy trabajando con Time in Rails y uso el siguiente código para configurar la fecha de inicio y la fecha de finalización de un proyecto:

start_date ||= Time.now end_date = start_date + goal_months.months

Luego clono el objeto y estoy escribiendo pruebas rspec para confirmar que los atributos coinciden en la copia. Las fechas de final coinciden:

original[end_date]: 2011-08-24 18:24:53 UTC clone[end_date]: 2011-08-24 18:24:53 UTC

pero la especificación me da un error en las fechas de inicio:

expected: Wed Aug 24 18:24:53 UTC 2011, got: Wed, 24 Aug 2011 18:24:53 UTC +00:00 (using ==)

Está claro que las fechas son las mismas, solo tienen un formato diferente. ¿Cómo es que acaban siendo almacenados de manera diferente en la base de datos, y cómo logro que coincidan? Lo he intentado con DateTime también con los mismos resultados.

Corrección: las fechas de finalización tampoco coinciden. Imprimen lo mismo, pero también errores de rspec en ellos. Cuando imprimo la fecha de inicio y la fecha de finalización, los valores salen en diferentes formatos:

start date: 2010-08-24T19:00:24+00:00 end date: 2011-08-24 19:00:24 UTC


¿Estás seguro de que estás usando == y no eql o be ? Los dos últimos métodos usan identidad de objeto en lugar de comparar valores.

Desde el resultado, parece que el valor esperado es un Time , mientras que el valor que se prueba es un DateTime . Esto también podría ser un problema, aunque dudaría en adivinar cómo solucionarlo dada la naturaleza casi patológica de las bibliotecas de fecha y hora de Ruby ...


Agregar .to_datetime a ambas variables .to_datetime a los valores de fecha y hora a ser equivalentes y respetar las zonas horarias y el horario de verano. Para las comparaciones de fechas, use .to_date .

Una especificación de ejemplo con dos variables: actual_time.to_datetime.should == expected_time.to_datetime

Una mejor especificación con claridad: actual_time.to_datetime.should eq 1.month.from_now.to_datetime

.to_i produce ambigüedad con respecto a su significado en las especificaciones.

+1 por usar la gema TimeCop en las especificaciones. Solo asegúrate de probar el horario de verano en tus especificaciones si tu aplicación se ve afectada por el horario de verano.


Debería burlarse del método now de Time para asegurarse de que siempre coincida con la fecha en la especificación. Nunca se sabe cuando un retraso hará que la especificación falle debido a algunos milisegundos. Este enfoque también asegurará que la hora en el código real y en la especificación sea la misma.

Si está utilizando el rspec mock lib predeterminado, intente hacer algo como:

t = Time.parse("01/01/2010 10:00") Time.should_receive(:now).and_return(t)


Dependiendo de sus especificaciones, es posible que pueda utilizar los ayudantes de viaje nativos de Rails:

# in spec_helper.rb config.include ActiveSupport::Testing::TimeHelpers start_date ||= Time.current.change(usecs: 0) end_date = start_date + goal_months.months travel_to start_date do # Clone here end expect(clone.start_date).to eq(start_date)

Sin Time.current.change(usecs: 0) es probable que se queje de la diferencia entre las zonas horarias. O entre los microsegundos, ya que el ayudante restablecerá el valor pasado internamente (Timecop tiene un problema similar, así que usecs con él también).


Esto generalmente ocurre porque rspec intenta hacer coincidir diferentes objetos: Time y DateTime, por ejemplo. Además, los tiempos comparables pueden diferir un poco, durante unos pocos milisegundos.

En el segundo caso, la forma correcta es usar el troquelado y el simulacro. También vea la gema TimeCop

En el primer caso, la solución posible puede ser comparar las marcas de tiempo:

actual_time.to_i.should == expected_time.to_i

Yo uso matcher simple para tales casos:

# ./spec/spec_helper.rb # # Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f} # # # Usage: # # its(:updated_at) { should be_the_same_time_as updated_at } # # # Will pass or fail with message like: # # Failure/Error: its(:updated_at) { should be_the_same_time_as 2.days.ago } # expected Tue, 07 Jun 2011 16:14:09 +0300 to be the same time as Mon, 06 Jun 2011 13:14:09 UTC +00:00 RSpec::Matchers.define :be_the_same_time_as do |expected| match do |actual| expected.to_i == actual.to_i end end


Estoy totalmente de acuerdo con las respuestas anteriores sobre el trozo Time.now. Eso dijo que hay otra cosa pasando aquí. Cuando comparas fechas y horas de una base de datos, pierdes parte del tiempo de facción que puede estar en un objeto DateTime de ruby. La mejor manera de comparar la fecha de esa manera en Rspec es:

database_start_date.should be_within(1).of(start_date)


Mi suposición inicial sería que el valor de Time.now está formateado de forma diferente al valor de su base de datos.


Nuestra solución actual es tener un método freeze_time que maneje el redondeo:

def freeze_time(time = Time.zone.now) # round time to get rid of nanosecond discrepancies between ruby time and # postgres time time = time.round Timecop.freeze(time) { yield(time) } end

Y luego puedes usarlo como:

freeze_time do perform_work expect(message.reload.sent_date).to eq(Time.now) end


Una solución que me gusta es agregar lo siguiente a spec_helper :

class Time def ==(time) self.to_i == time.to_i end end

De esta forma es completamente transparente incluso en objetos anidados.


Una sorpresa es que los objetos de Ruby Time tienen una precisión de nanosegundos, pero la mayoría de las bases de datos tienen una precisión de microsegundos máxima. La mejor forma de evitar esto es utilizar Time.now (o use timecop) con un número redondo. Lea la publicación que escribí sobre esto: http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/