So I’m playing with the mocks and stubs in RSpec and encountered a failing example. I’m setting the Task.completed_at column to Time.now in my controller like this:
def complete
@task = Task.find(params[:id])
@task.update_attribute :completed_at, Time.now
end
and this line in my RSpec example fails:
@task.should_receive(:update_attribute).with(:completed_at, Time.now)
It’s fairly obvious that Time.now is the problem. Don’t let the error message fool you!
... Mock 'Task_1002' expected :update_attribute with (:completed_at, Sat Nov 24 00:43:02 +0800 2007) but received it with (:completed_at, Sat Nov 24 00:43:02 +0800 2007) ...
The values being compared look the same in the error message, but as stated in the Ruby documentation, “The [time] object will be created using the resolution available on your system clock, and so may include fractional seconds.” I created a stub and my RSpec example now looks like this:
it "should tell the Task model to update the task's completed_at to the current time on POST to complete" do
# Stub
time_now = Time.now
Time.stub!(:now).and_return(time_now)
Task.should_receive(:find).with("1").and_return(@task)
@task.should_receive(:update_attribute).with(:completed_at, Time.now)
post 'complete', {:id => "1"}
end
I could have made time_now an instance variable, and moved it to before(:each) so that it can be reused by other RSpec examples. But at the moment, this is the only example that needs it.
Alternatives?
I Googled to check how the others implemented this. One of the results showed a year-old mailing list discussion of stubbing Time.now this way:
@time_now = Time.parse("Jan 1 2001")
Time.stubs(:now).returns(lambda{@time_now})
The discussion said that’s supposed to work before but there’s an error now regarding the number of arguments, and it was mentioned it could be a bug. Another suggested defining Time.now in spec_helper. Other results weren’t in the context of RSpec.
I’m satisfied with my code for now. I’ll make a helper in spec_helper if other specifications will need it.