Ruby On Rails : Changer la valeur de DateTime.now dans les tests automatiques

Cela peut être utile si vous avez un projet avec des tests automatiques qui se basent sur des heures ou des jours, comme par exemple une action ne redirigeant pas sur la même page selon l’heure de la journée. Si vous souhaitez tester cette action, l’heure d’exécution des tests ne produira pas le même résultat, soit les tests passeront soit vous obtiendrez une failure.

Comment « faire croire » aux tests que l’heure n’est pas celle du système mais une heure choisie par le développeur ?

En utilisant les tests automatiques natifs de Rails (Minitest), nous avons trouvé une solution qui consiste à redéfinir la classe DateTime et notamment la méthode now (méthode utilisée dans notre action), uniquement pour l’exécution des tests, qui indique quelle heure nous souhaiterions qu’il soit.

Dans test/test_helper.rb, ajouter :

# Sauvegarde du temps, car la redéfinition qui va suivre trafique DateTime.now
@@temps_au_lancement_des_tests = DateTime.now
 
# Redéfinition du DateTime.now
def redefinir_temps(temps = @@temps_au_lancement_des_tests)
  @@temps = temps
  class << DateTime
    def now
      @@temps
    end
  end
end

La méthode redefinir_temps permet de modifier le retour de DateTime.now utilisé dans notre action, en utilisant le datetime passé en paramètre, et ce uniquement dans l’environnement des tests. Attention, le retour de DateTime.now est désormais fixe et retournera systématiquement la valeur que nous avons passé en paramètre.

Utilisation

  test "doit réaliser la première redirection à 9h" do
    redefinir_temps(DateTime.new(@@temps_au_lancement_des_tests.year, @@temps_au_lancement_des_tests.month, @@temps_au_lancement_des_tests.day, 9)) # On redéfinit la méthode DateTime.now pour qu'elle retourne la date du jour à 9H
    get :redirection
    assert_redirected_to URL_DE_LA_PREMIERE_REDIRECTION
  end
 
 
  test "doit réaliser la seconde redirection à 6h" do
    redefinir_temps(DateTime.new(@@temps_au_lancement_des_tests.year, @@temps_au_lancement_des_tests.month, @@temps_au_lancement_des_tests.day, 6, 30)) # On redéfinit la méthode DateTime.now pour qu'elle retourne la date du jour à 6H30
    get :redirection
    assert_redirected_to URL_DE_LA_SECONDE_REDIRECTION
  end

Réinitialiser le comportement original de DateTime.now

Une fois le test effectué, nous réinitialisons le retour de DateTime.now à sa valeur réelle (pour ne pas perturber les autres tests).

Dans test/test_helper.rb, ajouter :

# Sauve la définition DateTime.now originale avant redéfinition
@@datetime_now_original = DateTime.method(:now)
# Écrase la redéfinition de DateTime.now
def reinitialiser_datetime_now
  class << DateTime
    def now
      @@datetime_now_original.call
    end
  end
end

Dans votre test :

reinitialiser_datetime_now

La date et l’heure qui seront à présent retournées par DateTime.now seront celles du système d’exploitation.

Astuce

Lorsqu’on redéfini DateTime.now pour retourner une date fixe, le code testé par la suite utilise une valeur de DateTime.now qui ne « s’écoule plus ». Si vous décidez que tous vos tests s’exécutent à l’heure du lancement des tests, vous pouvez le fixer à nouveau à la fin de chaque test via redefinir_temps(@@temps_au_lancement_des_tests).

2 réflexions au sujet de « Ruby On Rails : Changer la valeur de DateTime.now dans les tests automatiques »

  1. Petit complément, dans ActiveSupport, on dispose de `travel_to` qui permet de faire sensiblement la même chose en englobant le code à tester dans un bloc.

    « `ruby
    test_time = DateTime.new(2016, 1, 1, 13, 37, 0)
    travel_to test_time do
    assert_equal test_time, Time.current
    end
    « `

    Mais effectivement, cela ne fonctionne pas avec `DateTime.now` auquel on peut lui préférer `Time.current` qui renvoie une date avec la timezone configurée au niveau de l’application – et qui accessoirement fonctionne avec travel_to 🙂

    Bravo au passage pour votre site, sur le terrain depuis des années et pourtant toujours très intéressant !

    1. Bonjour et merci pour ton apport !

      Il faut dire que le principal problème de la solution proposée par cet article est que le temps ne s’écoule plus une fois redéfini. Je suis content de découvrir travel_to, malheureusement c’est un helper pour les tests, ce n’est même pas utilisable un dans contrôleur (alors qu’il doit bien y avoir des cas d’utilisation pour cela).

      http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

      To be continued…

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.