I am using Ruby on Rails 4 and the rspec-rails gem 2.14. For a my object I would like to compare the current time with the updated_at
object attribute after a controller action run, but I am in trouble since the spec does not pass. That is, given the following is the spec code:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at).to eq(Time.now)
end
When I run the above spec I get the following 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 ==)
How can I make the spec to pass?
Note: I tried also the following (note the utc
addition):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
but the spec still does not pass (note the "got" value difference):
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 ==)
yep as
Oin
is suggestingbe_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
andmiddnight
attributes.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:
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:
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:
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 thematch(hash)
:You can convert the date/datetime/time object to a string as it's stored in the database with
to_s(:db)
.I find using the
be_within
default rspec matcher more elegant:Old post, but I hope it helps anyone who enters here for a solution. I think it's easier and more reliable to just creating the date manually:
This ensures the stored date is the right one, without doing
to_x
or worrying about decimals.Ruby Time object maintains greater precision than the database does. When the value is read back from the database, it’s only preserved to microsecond precision, while the in-memory representation is precise to nanoseconds.
If you don't care about millisecond difference, you could do a to_s/to_i on both sides of your expectation
or
Refer to this for more information about why the times are different
The easiest way I found around this problem is to create a
current_time
test helper method like so:Now the time is always rounded to the nearest millisecond to comparisons are straightforward: