Why does the following test give me a TypeError: expected numeric
error:
it { expect(DateTime.now).to be_within(1.second).of Time.zone.now }
but this test does not?
it { expect(DateTime.now).to be_within(1.second).of Time.zone.now.to_datetime }
1
You can easily reproduce this outside of RSpec (and outside of ruby-on-rails)
DateTime.now - Time.now
#=> expected numeric (TypeError)
Had you posted the full stack trace it would have shown the error occurred in Date#minus_without_duration
(added by ActiveSupport
), which is simply an alias for Date#-
.
Ruby’s Date#-
method states:
Returns the difference between the two dates if the other is a date object. If the other is a numeric value, returns a date object pointing
other
days before self. If the other is a fractional number, assumes its precision is at most nanosecond.
Time
(or more accurately in this case ActiveSupport::TimeWithZone
) is not a Date
nor is it a Numeric
, thus the TypeError
.
When you convert it to a DateTime
, a DateTime
is a Date
, so the method functions appropriately.
In order for the comparison to work appropriate just make sure the 2 objects are #-
compatible (and technically that #-
returns a Numeric value or some other object that responds to abs
[see below]).
In your case you can accomplish this a number of ways including Time#to_datetime
(as shown), DateTime#in_time_zone
, DateTime#to_time
, etc.
How this relates to RSpec::Matchers::BuiltIn::BeWithin
:
The syntax in this case is expect(actual).to be_within(tolerance).of(expected)
The matcher processes a match as: (Source)
numeric? && (@actual - @expected).abs <= @tolerance
Which in this case equates to
actual = DateTime.now
tolerance = 1
expected = Time.zone.now
if actual.respond_to?(:-)
difference = actual - expected # Error occurs here
difference.abs <= tolerance
else
false
end