ruby on rails - Rails-Probando un método que usa DateTime.now
ruby-on-rails ruby-on-rails-4 (3)
Tengo un método que usa DateTime.now para realizar una búsqueda en algunos datos, quiero probar el método con varias fechas pero no sé cómo apagar DateTime.now ni puedo hacerlo funcionar con Timecop (si es que Funciona así).
Con el tiempo he intentado policia
it ''has the correct amount if falls in the previous month'' do
t = "25 May".to_datetime
Timecop.travel(t)
puts DateTime.now
expect(@employee.monthly_sales).to eq 150
end
cuando ejecuto la especificación puedo ver que pone DateTime.now da 2015-05-25T01:00:00+01:00
pero teniendo el mismo lugar pone DateTime.now dentro del método que estoy probando resultados 2015-07-24T08:57:53+01:00
(fecha de hoy). ¿Cómo puedo lograr esto?
------------------actualizar------------------------------- --------------------
Estaba configurando los registros (@employee, etc.) en un bloque before(:all)
que parece haber causado el problema. Solo funciona cuando la configuración se realiza después del bloqueo de tiempo de Timecop do
. ¿Por qué es este el caso?
Me encontré con varios problemas con Timecop y otras cosas mágicas que se meten con las clases de Fecha, Hora y Fecha y tiempo y sus métodos. Descubrí que es mejor usar simplemente la inyección de dependencia en su lugar:
Código de empleado
class Employee
def monthly_sales(for_date = nil)
for_date ||= DateTime.now
# now calculate sales for ''for_date'', instead of current month
end
end
Especulación
it ''has the correct amount if falls in the previous month'' do
t = "25 May".to_datetime
expect(@employee.monthly_sales(t)).to eq 150
end
Nosotros, las personas del mundo Ruby, encontramos un gran placer al utilizar algunos trucos de magia, que las personas que usan lenguajes de programación menos expresivos no pueden utilizar. Pero este es el caso en el que la magia es demasiado oscura y realmente debería evitarse. Simplemente utilice el método de mejor práctica generalmente aceptado de inyección de dependencia en su lugar.
Timecop debería ser capaz de manejar lo que quieras. Intente congelar el tiempo antes de ejecutar la prueba en lugar de solo viajar, luego descongelar cuando termine. Me gusta esto:
before do
t = "25 May".to_datetime
Timecop.freeze(t)
end
after do
Timecop.return
end
it ''has the correct amount if falls in the previous month'' do
puts DateTime.now
expect(@employee.monthly_sales).to eq 150
end
Del readme de Timecop:
congelar se utiliza para simular estáticamente el concepto de ahora. A medida que se ejecute su programa, Time.now no cambiará a menos que realice llamadas posteriores a la API de Timecop. por otro lado, viajar calcula un desplazamiento entre lo que actualmente creemos que Time.now (recuerda que admitimos viajes anidados) y el tiempo transcurrido. Utiliza este desplazamiento para simular el paso del tiempo.
Así que quieres congelar el tiempo en un lugar determinado, en lugar de simplemente viajar a ese tiempo. Ya que el tiempo pasará con un viaje como lo haría normalmente, pero desde un punto de partida diferente.
Si esto todavía no funciona, puedes poner tu método de llamada en un bloque con Timecop para asegurarte de que se está congelando el tiempo dentro del bloque como:
t = "25 May".to_datetime
Timecop.travel(t) do # Or use freeze here, depending on what you need
puts DateTime.now
expect(@employee.monthly_sales).to eq 150
end
TL; DR : el problema era que se llamaba a DateTime.now
en Employee
antes que a Timecop.freeze
en las especificaciones.
Timecop se burla del constructor de Time
, Date
y Date
y Time
. Cualquier instancia creada entre la freeze
y el return
(o dentro de un bloque de freeze
) se burlará.
Cualquier instancia creada antes de la freeze
o después de la return
no se verá afectada porque Timecop no se mete con los objetos existentes.
Desde el README (mi énfasis):
Una gema que ofrece capacidades de "viaje en el tiempo" y "tiempo de congelación", lo que hace que sea muy sencillo probar el código dependiente del tiempo. Proporciona un método unificado para simular Time.now, Date.today y DateTime.now en una sola llamada.
Por lo tanto, es esencial llamar a Timecop.freeze
antes de crear el objeto Time
que desea simular. Si se freeze
en un bloque RSpec before
, se ejecutará antes de que se evalúe el subject
. Sin embargo, si tiene un bloque before
donde configura su tema ( @employee
en su caso), y tiene otro bloque before
en una describe
anidada, entonces su tema ya está configurado, ya que llamó a DateTime.new
antes de que se congelara.
¿Qué sucede si agrega lo siguiente a su Employee
class Employee
def now
DateTime.now
end
end
A continuación, ejecute la siguiente especificación:
describe ''#now'' do
let(:employee) { @employee }
it ''has the correct amount if falls in the previous month'', focus: true do
t = "25 May".to_datetime
Timecop.freeze(t) do
expect(DateTime.now).to eq t
expect(employee.now).to eq t
expect(employee.now.class).to be DateTime
expect(employee.now.class.object_id).to be DateTime.object_id
end
end
end
En lugar de usar un bloque de freeze
, también puede freeze
y return
en rspec before
y after
ganchos:
describe Employee do
let(:frozen_time) { "25 May".to_datetime }
before { Timecop.freeze(frozen_time) }
after { Timecop.return }
subject { FactoryGirl.create :employee }
it ''has the correct amount if falls in the previous month'' do
# spec here
end
end
Fuera del tema, pero tal vez eche un vistazo a http://betterspecs.org/