Test Time Bombs

Nick Quaranto

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.