paint-brush
Timecop: A Ruby Gem to Write Time-Sensitives Testsby@maclenn77
531 reads
531 reads

Timecop: A Ruby Gem to Write Time-Sensitives Tests

by J.P. Pérez-TejadaMarch 5th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Timecop is a ruby gem created for those specific cases where we have tests dependent on time. It could be birthdays, but also it could be expiration days for JWT tokens or recurrent billings. Timecop creates a mock of Time.now, Date.today, and DateTime.now. With Timecop, you control the time of your tests!
featured image - Timecop: A Ruby Gem to Write Time-Sensitives Tests
J.P. Pérez-Tejada HackerNoon profile picture

Time is a huge topic. I mean, since the start of the times. Our species has thinking about time since a millennia ago. We don’t know the precise date when that happened because we didn’t take account of time until we ponder a lot about it. So, we could sometimes overlook some nuances of time. And that happened to the team that wrote some tests in a Rails project.

Once Upon a Time, There Was a Ruby Project Dealing With Time…

I started collaborating on that project where I took a tech-lead role. I was performing more code reviews than coding. And one of the first things that popped up to my eye when reviewing pull requests was that the team was changing tests even when the change was a refactor.


“Why are you rewriting tests for a refactor? In a refactor, tests should remain the same. That tells us that the refactored code is working in the same way as the older code! ” I added in my comments.


The frustrated team members pointed out that each week, at least one test fails because it depends on the date. One app controller retrieves the results of people whose birthday occurs on that week. If they run the test suite in a different week, they need to update the expected result.


I was shocked! I was planning to add a task for refactoring those tests until the product owner said, “No! On this point, we need to create stories that add value to the final user. Tests are important, but they don’t add value to the final user!”


I didn’t want to argue, so I accepted the ridiculous reality with stoicism. I continued performing code reviews, and I learned to ignore changes on tests.


But after a while, I found the time to tackle those disgraceful test cases. And I found the gem «timecop», a gem created by John Trupiano and maintained by Travis Jeffery.

What Does Timecop Do?

Timecop is a ruby gem created for those specific cases where we have tests dependent on time. It could be birthdays, but also it could be expiration days for JWT tokens or recurrent billings.


In a single line, Timecop creates a mock of Time.now, Date.today, and DateTime.now. As Timecop creators highlight in their README file, the gem doesn’t have dependencies, so it works well with any Ruby test framework.


With only two basic methods (Time.travel and Time.freeze), you can easily control the time!

With Timecop, you control the time of your tests!

How to Install Timecop

Timecop is a ruby gem available on rubygems.org. So, you can just add Timecop to your Gemfile and run bundle install after that. Unless you're using Timecop for something different to testing, add the gem in a test group.


Gemfile

...
group :test do
  ... # Other gems
  gem 'timecop'
end


Alternatively, run bundle add timecop or install it on your computer with gem install timecop.

To Timecop.travel or To Timecop.freeze?

As I commented above, you only need to know two methods for using Timecop: Timecop.travel and Timecop.freeze. But what is the difference between these two methods?

Timecop.travel Method

With Timecop.travel , you set up time to a specific date, and then time will move forward after that.


For example, if you Timecop.travel to February 2, 1993, 6:00:00 AM, after ten seconds, Time.now returns February 2, 1993, 6:00:10 AM

groundhog_day = Time.local(1993, 2, 2, 6, 0, 0)
Timecop.travel(groundhog_day)
sleep(10)
groundhog_day == Time.now # ==> false

Timecop.freeze Method

On the other way, with Timecop.freeze time stops completely. So, using a similar example, Time.now will return exactly on February 2, 1993, 6:00:00 AM:


groundhog_day = Time.local(1993, 2, 2, 6, 0, 0)
Timecop.travel(groundhog_day)
sleep(10)
groundhog_day == Time.now # ==> true


Time is completely frozen now!

Other Useful Methods

Other Timecop methods allow you to move time faster or return to the system date.

Timecop.scale

Move times faster. It’s a good option for integration tests where you want to check that some jobs are performed. Code example:


# seconds will now seem like hours
Timecop.scale(3600)
Time.now
# => 2012-09-20 21:23:25 -0500
# seconds later, hours have passed and it's gone from 9pm at night to 6am in the morning
Time.now
# => 2012-09-21 06:22:59 -0500


Timecop.return

Move time to system date again.


GROUNDHOG_DAY = Time.local(1993, 2, 2, 23, 59, 51)

def groundhog_day? = Time.now == GROUNDHOG_DAY

Timecop.freeze(GROUNDHOG_DAY)
sleep(10)

groundhog_day? # => True

Timecop.return

groundhog_day? #=> False

Set the Time of Your Tests’ Cases

Timecop readme is really clear for its usage. For using Timecop in a time-sensitive test:


joe = User.find(1)
joe.purchase_home()
assert !joe.mortgage_due?
# move ahead a month and assert that the mortgage is due
Timecop.freeze(Date.today + 30) do
  assert joe.mortgage_due?
end


For use in a group of tests, you write:

describe "some set of tests to mock" do
  before do
    Timecop.freeze(Time.local(1990))
  end

  after do
    Timecop.return
  end

  it "should do blah blah blah" do
  end
end

Set the Start Time of Your Test Environment for Your Rails Project

If you want to set up your test environment to always start on the same date, add this configuration:


config/environment/ test.rb

... # your configuration
  config.after_initialize do
    # Set Time.now to February 2, 1993 06:00:00 AM (at this instant), but allow it to move forward
    # year, month, day, hour, min, sec
    t = Time.local(1993, 2, 2, 6, 0, 0)
    Timecop.travel(t)
  end
...
end


After that, each time that you run your test, the date will start on February 2 at 6:00 AM.


Always the same date!


That’s all! I hope you found Timecop as useful as this post.