Comment simuler Time.now?

Quelle est la meilleure façon de définir Time.now pour tester des méthodes sensibles au temps dans un test unitaire?

J’aime vraiment la bibliothèque Timecop . Vous pouvez faire des warps de temps sous forme de bloc (tout comme time-warp):

 Timecop.travel(6.days.ago) do @model = TimeSensitiveMode.new end assert @model.times_up! 

(Oui, vous pouvez imbriquer le voyage dans le temps en forme de bloc.)

Vous pouvez également faire un voyage temporel déclaratif:

 class MyTest < Test::Unit::TestCase def setup Timecop.travel(...) end def teardown Timecop.return end end 

J'ai des aides de concombre pour Timecop ici . Ils vous ont laissé faire des choses comme:

 Given it is currently January 24, 2008 And I go to the new post page And I fill in "title" with "An old post" And I fill in "body" with "..." And I press "Submit" And we jump in our Delorean and return to the present When I go to the home page I should not see "An old post" 

Personnellement, je préfère rendre l’horloge injectable, comme ceci:

 def hello(clock=Time) puts "the time is now: #{clock.now}" end 

Ou:

 class MyClass attr_writer :clock def initialize @clock = Time end def hello puts "the time is now: #{@clock.now}" end end 

Cependant, beaucoup préfèrent utiliser une bibliothèque moqueuse / stupide. Dans RSpec / flexmock, vous pouvez utiliser:

 Time.stub!(:now).and_return(Time.mktime(1970,1,1)) 

Ou à Mocha:

 Time.stubs(:now).returns(Time.mktime(1970,1,1)) 

J’utilise RSpec et je l’ai fait: Time.stub! (: Now) .and_return (2.days.ago) avant d’appeler Time.now. De cette façon, je suis capable de contrôler le temps que j’ai utilisé pour ce cas de test particulier

Fais le temps

time-warp est une bibliothèque qui fait ce que vous voulez. Il vous donne une méthode qui prend du temps et un bloc et tout ce qui se passe dans le bloc utilise le temps simulé.

 pretend_now_is(2000,"jan",1,0) do Time.now end 

En utilisant Rspec 3.2, la seule façon simple de trouver la valeur de retour de Time.now est simple:

 now = Time.parse("1969-07-20 20:17:40") allow(Time).to receive(:now) { now } 

Maintenant, Time.now restituera toujours la date d’Apollo 11 à l’atterrissage sur la lune.

Source: https://www.relishapp.com/rspec/rspec-mocks/docs

N’oubliez pas que Time est simplement une constante qui fait référence à un object de classe. Si vous êtes prêt à provoquer un avertissement, vous pouvez toujours le faire

 real_time_class = Time Time = FakeTimeClass # run test Time = real_time_class 

Voir aussi cette question où je mets aussi ce commentaire.

Selon ce que vous comparez Time.now à, vous pouvez parfois modifier vos appareils pour atteindre le même objective ou tester la même fonctionnalité. Par exemple, j’avais une situation où il fallait que quelque chose se produise si une date était à venir et une autre si cela se passait dans le passé. Ce que j’ai pu faire était d’inclure dans mes appareils des rbuy (erb) intégrés:

 future: comparing_date: < %= Time.now + 10.years %> ... past: comparing_date: < %= Time.now - 10.years %> ... 

Ensuite, dans vos tests, vous choisissez lequel utiliser pour tester les différentes fonctionnalités ou actions en fonction du temps relatif à Time.now .

J’ai eu le même problème, j’ai dû faire un faux temps pour une spécification pour un jour et une heure bien précis:

 Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28)) 

cela vous donnera:

 2014-10-22 05:35:28 -0700 

Ce genre de travaux et permet l’imbrication:

 class Time class < < self attr_accessor :stack, :depth end def self.warp(time) Time.stack ||= [] Time.depth ||= -1 Time.depth += 1 Time.stack.push time if Time.depth == 0 class << self alias_method :real_now, :now alias_method :real_new, :new define_method :now do stack[depth] end define_method :new do now end end end yield Time.depth -= 1 Time.stack.pop class << self if Time.depth < 0 alias_method :new, :real_new alias_method :now, :real_now remove_method :real_new remove_method :real_now end end end end 

Il pourrait être légèrement amélioré en libérant les accesseurs de stack et de profondeur à la fin.

Usage:

 time1 = 2.days.ago time2 = 5.months.ago Time.warp(time1) do Time.real_now.should_not == Time.now Time.now.should == time1 Time.warp(time2) do Time.now.should == time2 end Time.now.should == time1 end Time.now.should_not == time1 Time.now.should_not be_nil 

Selon ce que vous comparez Time.now à, vous pouvez parfois modifier vos appareils pour atteindre le même objective ou tester la même fonctionnalité. Par exemple, j’avais une situation où il fallait que quelque chose se produise si une date était à venir et une autre si cela se passait dans le passé. Ce que j’ai pu faire était d’inclure dans mes appareils des rbuy (erb) intégrés:

 future: comparing_date: < %= Time.now + 10.years %> ... past: comparing_date: < %= Time.now - 10.years %> ... 

Ensuite, dans vos tests, vous choisissez lequel utiliser pour tester les différentes fonctionnalités ou actions en fonction du temps relatif à Time.now .

Je viens de l’avoir dans mon fichier de test:

  def time_right_now current_time = Time.parse("07/09/10 14:20") current_time = convert_time_to_utc(current_date) return current_time end 

et dans mon fichier Time_helper.rb j’ai un

  def time_right_now current_time= Time.new return current_time end 

ainsi, lorsque vous testez le time_right_now, il est écrasé pour utiliser le temps que vous souhaitez qu’il soit.

Time.now toujours Time.now dans une méthode séparée que je transforme en attr_accessor dans le simulacre.

Test::Redef récemment publié, Test::Redef cette opération et d’autres , même sans restructurer le code dans un style d’dependency injection (particulièrement utile si vous utilisez le code d’autres personnes).

 fake_time = Time.at(12345) # ~3:30pm UTC Jan 1 1970 Test::Redef.rd 'Time.now' => proc { fake_time } do assert_equal 12345, Time.now.to_i end 

Cependant, Date.new attention aux autres moyens d’obtenir du temps pour que cela ne se reproduise pas ( Date.new , une extension compilée qui fait son propre appel système, interface avec des choses comme les serveurs de bases de données externes qui connaissent les horodatages actuels, etc.) La bibliothèque Timecop ci-dessus pourrait surmonter ces limitations.

Parmi les autres utilisations intéressantes, on peut citer des tests tels que “que se passe-t-il lorsque j’essaie d’utiliser ce client http convivial mais qu’il décide de déclencher une exception au lieu de me renvoyer une chaîne?” sans mettre en place les conditions du réseau qui conduisent à cette exception (ce qui peut être délicat). Il vous permet également de vérifier les arguments pour redéfinir les fonctions.

Si vous avez inclus ActiveSupport, vous pouvez utiliser http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html