I have a method that uses DateTime.now to perform a search on some data, I want to test the method with various dates but I don't know how to stub DateTime.now nor can I get it working with Timecop ( if it even works like that ).
With time cop I tried
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
when I run the spec I can see that puts DateTime.now gives 2015-05-25T01:00:00+01:00
but having the same puts DateTime.now within the method I'm testing outputs 2015-07-24T08:57:53+01:00
(todays date).
How can I accomplish this?
------------------update---------------------------------------------------
I was setting up the records (@employee, etc.) in a before(:all)
block which seems to have caused the problem. It only works when the setup is done after the Timecop do
block. Why is this the case?
TL;DR: The problem was that DateTime.now
was called in Employee
before Timecop.freeze
was called in the specs.
Timecop mocks the constructor of Time
, Date
and DateTime
. Any instance created between freeze
and return
(or inside a freeze
block) will be mocked.
Any instance created before freeze
or after return
won't be affected because Timecop doesn't mess with existing objects.
From the README (my emphasis):
A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.
So it is essential to call Timecop.freeze
before you create the Time
object you want to mock. If you freeze
in an RSpec before
block, this will be run before subject
is evaluated. However, if you have a before
block where you set up your subject (@employee
in your case), and have another before
block in a nested describe
, then your subject is already set up, having called DateTime.new
before you froze time.
What happens if you add the following to your Employee
class Employee
def now
DateTime.now
end
end
Then you run the following spec:
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
Instead of using a freeze
block, you can also freeze
and return
in rspec before
and after
hooks:
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
Off-topic, but maybe have a look at http://betterspecs.org/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With