Timecop n’est pas une nouvelle gem. Mais je ne la découvre qu’aujourd’hui. Ça fait pourtant 4 ans que je travaille quotidiennement avec Ruby et/ou Rails… Et je ne me suis jamais sentis très à l’aise pour tester les dates. Avec Timecop, ça devient un jeu d’enfant.

Pour voir un souci possible, prenons la classe suivante qui représente un article:

class Article
  def initialize(name)
    @name = name
    @created_at = Time.now
  end

  attr_reader :name, :created_at
end

Dans le monde réel, ce serait surement un ActiveRecord, mais pour l’exemple on se contentera bien de cette classe ;)

Maintenant testons la création d’un article:

require_relative "article"
require "test/unit"

class TestArticle < Test::Unit::TestCase
  def test_creation
    article = Article.new('Foo')
    assert_equal 'Foo', article.name
    assert_equal Time.now, article.created_at
  end
end

Et voilà le souci, à quelques nano-secondes près ça pète:

$ ruby tc_article.rb 
Run options: 

# Running tests:

[1/1] TestArticle#test_creation = 0.00 s
  1) Failure:
TestArticle#test_creation [tc_article.rb:8]:
<2014-04-01 21:05:10 +0200> (204201[ns]) expected but was
<2014-04-01 21:05:10 +0200> (176685[ns]).

Finished tests in 0.009979s, 100.2062 tests/s, 200.4125 assertions/s.
1 tests, 2 assertions, 1 failures, 0 errors, 0 skips

Timecop est la meilleure solution que j’ai vu jusqu’ici pour régler ce type de problème. La gem propose tout simplement (entre autres) de geler le temps:

require_relative "article"
require "test/unit"
require "timecop"

class TestArticle < Test::Unit::TestCase
  def test_creation
    Timecop.freeze do
      article = Article.new('Foo')
      assert_equal 'Foo', article.name
      assert_equal Time.now, article.created_at
    end
  end
end

Et voilà le résultat:

ruby tc_article.rb 
Run options: 

# Running tests:

Finished tests in 0.010756s, 92.9747 tests/s, 185.9494 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

J’aimerais bien savoir quelles solutions vous avez adoptés pour régler ce genre de soucis…

À demain.