web-dev-qa-db-fra.com

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?

68
user147962

J'aime beaucoup la bibliothèque Timecop . Vous pouvez faire des warps temporels sous forme de bloc (comme pour time-warp):

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

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

Vous pouvez également faire un voyage dans le temps déclaratif:

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

J'ai quelques concombre helpers 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"
72
James A. Rosen

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 de moquage. Dans RSpec/flexmock, vous pouvez utiliser:

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

Ou en moka:

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

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

14
Staelen

Faites le time-warp

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

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

En utilisant Rspec 3.2, le seul moyen simple que j'ai trouvé de simuler une valeur de retour Time.now est:

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

Maintenant, Time.now renverra toujours la date d’atterrissage d’Apollo 11 sur la lune.

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

8
Iwazaru

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

real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
7
Andrew Grimm

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

En fonction de ce que vous comparez à Time.now, vous pouvez parfois changer vos appareils pour atteindre le même objectif ou tester la même fonctionnalité. Par exemple, je me trouvais dans une situation où il me fallait une chose si quelque date était future et une autre si c'était dans le passé. Ce que j'ai pu faire, c'est d'inclure dans mes appareils un Ruby intégré (erb):

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 par rapport à Time.now.

1
Bryan Ward

Si j'avais le même problème, je devais simuler du temps pour une spécification pour un jour et une heure spécifiques, mais je viens de le faire

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

cela vous donnera:

2014-10-22 05:35:28 -0700
1
Netta D

Ce type de travaux permet et permet d'imbriquer: 

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 décompressant les accessoires de pile 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
0
Sam Saffron

j'ai juste cela 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, lors du test, time_right_now est écrasé pour utiliser le temps que vous souhaitez 

0
Mo.

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

0
Artur Beljajev

J'extrais toujours Time.now dans une méthode distincte que je transforme en attr_accessor dans la maquette.

0
hakunin

Le Test::Redef publié récemment rend cette et toute autre falsification facile, même sans restructurer le code dans un style d'injection de dépendances (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, faites attention aux autres moyens d'obtenir du temps sans fausse impression (Date.new, une extension compilée qui effectue son propre appel système, des interfaces avec des serveurs de base de données externes qui connaissent les horodatages actuels, etc.). Cela ressemble à la bibliothèque Timecop. ci-dessus pourrait surmonter ces limitations.

Parmi les autres utilisations intéressantes, citons le test suivant: "que se passe-t-il lorsque j'essaie d'utiliser ce client HTTP convivial, mais il décide de créer une exception au lieu de me retourner une chaîne?" sans définir réellement les conditions de 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.

0
fennec

En fonction de ce que vous comparez à Time.now, vous pouvez parfois changer vos appareils pour atteindre le même objectif ou tester la même fonctionnalité. Par exemple, je me trouvais dans une situation où il me fallait une chose si quelque date était future et une autre si c'était dans le passé. Ce que j'ai pu faire, c'est d'inclure dans mes appareils un Ruby intégré (erb):

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 par rapport à Time.now.

0
Bryan Ward