If you use Timecop within FactoryGirl factories, you get unexpected results, and many created_at
times are actually set to current real-time. I've created an example rails project that somewhat illustrates this issue. The book_spec shows some relevant examples.
Example
I have need of a factory that does a bunch of things in the past. I wanted to do this because the factory is needed in a large number of specs, and I wanted to save the time of doing these creation loops in every before
filter.
A very simplified and domain-changed example of what I'm doing is in the :published_book factory:
factory :book do
sequence(:title){|n|"Book#{n}"}
factory :published_book do
ignore do
days_ago 10
end
after :create do |book, evaluator|
Timecop.travel(evaluator.days_ago) do
book.update_attribute :created_at, Time.now
2.times do
authorship = book.authorships.create(FactoryGirl.build(:authorship, book: book, author: FactoryGirl.create(:author)).attributes.symbolize_keys)
authorship.save!
end
end
evaluator.days_ago.times.to_a.reverse.each do |i|
Timecop.travel(i.days.ago) do
book.authors.each do |author|
author.write_chapter! book
end
end
end
end
end
end
As you can see, I need to perform functions on the relationship between the authors and books starting some time ago and moving forward. I don't want to perform these functions in every test situation.
Timecop inside the factory fails
This all works well, except that it turns out that using Timecop to create the authorships
in the after :create
filter doesn't really work (also, the update_attribute
call to book has been added for illustration).
Using this factory, the following tests fail:
before do
@book = FactoryGirl.create :published_book
end
specify {@book.created_at.should be_within(1.hour).of(10.days.ago)}
specify {@book.authors.first.created_at.should be_within(1.hour).of(10.days.ago)}
Because the creation time of both @book
and all authors
is current real time, not 10.days.ago
as expected
The way it looks, Timecop cannot work well within a FactoryGirl factory and force the creation of models with proper times. Is this expected?
I should note that the following tests pass
before do
Timecop.travel(10.days.ago)
@book = FactoryGirl.create :published_book
end
end
specify {@book.created_at.should be_within(1.hour).of(10.days.ago)}
specify {@book.authors.first.created_at.should be_within(1.hour).of(10.days.ago)}
However, I'm worried about this because we're sort of avoiding Timecop's stated abilities and going back in time only to go back in time. I'm not sure it's going to screw something else up. To wit: The following fails:
it "Shouldn't go too far back in time" do
Timecop.travel(1.day.ago) do
Timecop.travel(3.days.ago) do
@oldbook = FactoryGirl.create :book
end
end
@oldbook.created_at.should_not be_within(1.hour).of(4.days.ago)
end
which suggests that the above workaround is, as I suspected, dangerous.
The only thing I can think to do now is to pull the Timecop stuff out of the factory, which necessitates building this loop code in a separate method. I'm not sure if the problem can actually be fixed, but wanted to know for sure.