yep as Oin is suggesting be_within matcher is the best practice
...and it has some more uscases -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
But one more way how to deal with this is to use Rails built in midday and middnight attributes.
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
Now this is just for demonstration !
I wouldn't use this in a controller as you are stubbing all Time.new calls => all time attributes will have same time => may not prove concept you are trying to achive. I usually use it in composed Ruby Objects similar to this:
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
But honestly I recommend to stick with be_within matcher even when comparing Time.now.midday !
So yes pls stick with be_within matcher ;)
update 2017-02
Question in comment:
what if the times are in a Hash? any way to make expect(hash_1).to eq(hash_2) work when some hash_1 values are pre-db-times and the corresponding values in hash_2 are post-db-times? –
expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `
you can pass any RSpec matcher to the match matcher (so e.g. you can even do API testing with pure RSpec)
As for "post-db-times" I guess you mean string that is generated after saving to DB. I would suggest decouple this case to 2 expectations (one ensuring hash structure, second checking the time) So you can do something like:
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)
But if this case is too often in your test suite I would suggest writing your own RSpec matcher (e.g. be_near_time_now_db_string) converting db string time to Time object and then use this as a part of the match(hash) :
expect(hash).to match({mytime: be_near_time_now_db_string}) # you need to write your own matcher for this to work.
===, but that may suffer from crossing second boundaries. Probably best is to find or write your own matcher, in which you convert to epoch seconds and allow for a small absolute difference.===instead of==- currently you are comparing the object_id of two different Time objects. Although Timecop won't freeze database server time . . . so if your timestamps are being generated by the RDBMS it wouldn't work (I expect that is not a problem for you here though)