I’m trying to test the following (simplified) model:
class Allocation < ActiveRecord::Base
scope :in_interval, (proc do |start_of_interval, end_of_interval|
params = {:s => start_of_interval, :e => end_of_interval}
where("(starts_at > :s AND starts_at < :e) OR (ends_at > :s AND
ends_at < :e) OR (starts_at <= :s AND ends_at >= :e)", params)
end)
scope :on_day, (proc do |day|
day = day.to_time.beginning_of_day
in_interval(day, day + 1.day)
end)
validations and other scopes…
end
I wrote a lot of specs for :in_interval - however testing :on_day is
kind of problem. I don’t want to duplicate any :in_interval specs -
therefore I’m trying to stub :in_interval like this:
let(:start_of_day) { Time.zone.now.beginning_of_day }
it do
Allocation.should_receive(:in_interval).with(start_of_day,
start_of_day + 1.day).and_return(“result”)
working assertion - the :in_interval stub seems to get called as
expected:
Allocation.on_day(start_of_day + 3.hours)
failing assertion:
Allocation.on_day(start_of_day + 3.hours).should == “result”
end
Your expectation (should_receive) is expecting “start_of_day”, which
uses Time.zone. The actual “on_day” scope does
“day.to_time.beginning_of_day”, which does not use any time zone.
Therefore, the arguments to in_interval are not the same as the
expectation. And because they are not the same, the mock does not get
set. They must be exactly the same, since you are using a specific
values.
You are not seeing a “the in_interval method was not called”
expectation ouput message because of the “includes_values” error. This
is because RSpec is comparing “result” with an array. This is because
Rails scopes return arrays, not strings (it is not returning a string
because the mock was never set).
Thanks for your suggestion Justin, but I don’t believe that the problem
is time zone related. Time objects usually don’t “loose” their Time Zone
when performing operations on them. Here’s an example for illustration:
$ rails console
Loading development environment (Rails 3.0.4)
ruby-1.8.7-p330 :001 > Time.zone.name
=> “Vienna”
ruby-1.8.7-p330 :002 > t = Time.zone.now.beginning_of_month
=> Tue, 01 Feb 2011 00:00:00 CET +01:00
ruby-1.8.7-p330 :003 > t += 3.hours
=> Tue, 01 Feb 2011 03:00:00 CET +01:00
ruby-1.8.7-p330 :004 > t.beginning_of_day
=> Tue, 01 Feb 2011 00:00:00 CET +01:00
If the should_receive arguments and actual arguments wouldn’t be the
same, then I would expect both examples below to fail.
context “.on_day” do
let(:start_of_day) { Time.zone.now.beginning_of_day }
before { Interval.should_receive(:in_interval).with(start_of_day,
start_of_day + 1.day).and_return(“result”) }
# (1) Passing example:
it { Interval.on_day(start_of_day + 3.hours) }
# (2) Failing example:
it { Interval.on_day(start_of_day + 3.hours).should == “result” }
end
However, (1) is passing and (2) is failing. Output as before: