Timecop Time and Database Time

June 8, 2014

[rails] [testing]

Timecop is a great addition to your testing toolbox, but if you’ve ever tried to use Timecop to interact with a database, and then got the most mysterious of existential errors:

expected: Sun, 08 Jun 2014 19:18:22 UTC +00:00
     got: Sun, 08 Jun 2014 19:18:22 UTC +00:00

I feel your pain.

Stop—Overtaken by Events

Timecop used to be the go-to for this in a Rails application, but Rails has had its own time travel tools since 4.1. It even has matchers that handle this particular pain point. You should consider using the built-in tools before adding another gem to your project.

Rails documentation: https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

Using it in RSpec: https://andycroll.com/ruby/replace-timecop-with-rails-time-helpers-in-rspec/

The trouble comes from the fact that Timecop is freezing time at the resolution of the native Time class, which on my Mac OS X machine is microseconds (aka “usec”), but the database, in this case MySQL only records time to the second. RSpec is trying to be helpful with its matchers by calling <code>#to_s`, but that hides the significant, sub-second difference.

Here’s a quick little monkey patch that I added to my spec support:

class Timecop
  def self.database_compatible_time(now = Time.zone.now)
    now.change(usec: 0)
  end
end

This uses a Rails helper to reset the microsecond portion of the time object. So, instead of

now = Time.zone.now

Timecop.freeze(now) do
  ...
end

I do this:

now = Timecop.database_compatible_time

Timecop.freeze(now) do
  ...
end

And no more head scratching. It’s sort of database specific, so your mileage may vary.

Timecop Time and Database Time - June 8, 2014 - Ken Mayer