Use Timecop to time travel in testing

Use Timecop to time travel in testing

Timecop is a gem providing “time travel” and “time freezing” capabilities, making it dead simple to test time-dependent code.

In this post, we are going to use the [Timecop](https://github.com/travisjeffery/timecop) ruby gem to test our trial_remaining_time method which returns the number of days remaining in the user's trial plan.

timecop is a gem providing time travel and time freezing capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.

class User < ApplicationRecord
  # Method to return number of remaining trial days
  def trial_remaining_days
    days = (created_at.to_date - 15.days.ago.to_date).to_i
    return days if days.positive?

    0
  end
end

And we want to make sure our method is working fine, so how do we test it? Using timecop makes it dead simple to travel time and freeze it so that we can check expected results on different future dates for our use case.

NOTE: I am using the default unit_test from the rails project, you can also use rspec without any issue.

require 'test_helper'

class UserTest < ActiveSupport::TestCase
  test 'should return remaining days for user' do
    user = User.new(created_at: Time.current)

    (15..0).to_a.each_with_index do |day, index|
      Timecop.freeze(day.days.from_now) do
        assert_equal index, user.trial_remaining_days
      end
    end
  end

  test 'should return 0 trial remaining days for user created more than 15 days ago' do
    user = User.new(created_at: Time.current)

    Timecop.freeze(16.days.from_now) do
      assert_equal 0, user.trial_remaining_days
    end

    Timecop.freeze(20.days.from_now) do
      assert_equal 0, user.trial_remaining_days
    end
  end
end
Running 2 tests in a single process (parallelization threshold is 50)
Run options: --seed 62828

# Running:

..

Finished in 0.038093s, 52.5033 runs/s, 52.5033 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

To understand it let's print values of Timecop.freeze method

# Today is 2022-01-11
Time.now
=> 2022-01-11 19:45:44.501290989 +0545

# lets freeze time in 0.days and see what will happens
irb(main):008:0> Timecop.freeze(0.days.from_now) { puts Time.current }
2022-01-11 14:01:00 UTC

# if we freeze time in 10.days from now
irb(main):010:0> Timecop.freeze(10.days.from_now) { puts Time.now }
2022-01-21 19:47:05 +0545

happy hacking!