Testing with times and dates have come a long way, now that
Timecop is around. Prior to Timecop,
we would use a stubbing library like Mocha to
stub out just what we needed, but why do that when Van Damme could beat the
right values out of Time
?
The Bomb
I wrote this Cucumber feature, models have been changed to protect the innocent.
Scenario: List latest posts under a tag
Given the following posts exist:
| Post | Tag list | Created on | Expires on |
| post1 | yankees | May 1, 2010 | July 1, 2010 |
| post2 | yankees | June 1, 2010 | July 1, 2010 |
| post3 | yankees | May 5, 2010 | July 1, 2010 |
| post4 | yankees | May 30, 2010 | July 1, 2010 |
| post5 | yankees | May 29, 2010 | July 1, 2010 |
When I visit the tags page
And I follow "yankees"
Then I should see the following posts in order:
| title |
| post2 |
| post4 |
| post5 |
| post3 |
| post1 |
Most of these steps are from
factory_bot
and webrat, except for the last one, which
parses the page using Nokogiri and looks for the titles in the order listed.
This feature passed when I wrote it (June 30th). Of course, on July 1st, I ran
the test again, and FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
.
Timecop wasn’t too helpful here, because the code actually used MySQL’s NOW()
function instead of accepting a date parameter for today. So that’s one thing I
could have fixed instead, but I didn’t want to touch any other code in the app.
Another obvious solution: bump up the expired date by a year, forget about it until next year. If you’re thinking of this solution, try imagining this guy kicking you in the face:
Future-proofing
Van Damme’s given us another chance. Let’s refactor this feature.
Scenario: List latest posts under a tag
Given the following posts with tags exist:
| title | tag list | created |
| post1 | yankees | 2.months.ago |
| post2 | yankees | 1.month.ago + 4.days |
| post3 | yankees | 1.month.ago |
| post4 | yankees | 1.month.ago + 2.days |
| post5 | yankees | 1.month.ago + 1.day |
When I visit the tags page
And I follow "yankees"
Then I should see the following posts in order:
| title |
| post2 |
| post4 |
| post5 |
| post3 |
| post1 |
Using a bit of ActiveSupport’s time helpers, we can future proof this step by
always moving forward instead of locking to a specific date. The step definition
then uses eval
to get the real dates back:
Given /^the following posts with tags exist:$/ do |table|
table.hashes.each do |row|
created_on = eval(row['created'])
Factory(:post, :title => row['title'],
:tag_list => row['tag list'],
:created_on => created_on,
:expires_on => created_on + 1.year)
end
end
KABOOM
So this is a simple way of avoiding time bombs in your tests. There’s no real way to prevent them always, you just need to consider how you’re dealing with time based code. If you’ve left time bombs in your test suites, feel free to own up and share your story too. Before Van Damme gets you.