edge
Plus sur rubyonrails.org: Plus de Ruby on Rails

Tester les applications Rails

Ce guide couvre les mécanismes intégrés dans Rails pour tester votre application.

Après avoir lu ce guide, vous saurez :

Chapters

  1. Pourquoi écrire des tests pour vos applications Rails ?
  2. Introduction aux tests
  3. Tests parallèles
  4. La base de données de test
  5. Test des modèles
  6. Test du système
  7. Tests d'intégration
  8. Tests fonctionnels pour vos contrôleurs
  9. Test des routes
  10. Test des vues
  11. Testing Helpers
  12. Testing Your Mailers
  13. Tests d'emploi
  14. Test de Action Cable
  15. Test de chargement anticipé
  16. Ressources de test supplémentaires

1 Pourquoi écrire des tests pour vos applications Rails ?

Rails facilite grandement l'écriture de vos tests. Il commence par produire du code de test squelette pendant que vous créez vos modèles et contrôleurs.

En exécutant vos tests Rails, vous pouvez vous assurer que votre code respecte la fonctionnalité souhaitée même après une refonte majeure du code.

Les tests Rails peuvent également simuler des requêtes de navigateur et vous permettre de tester la réponse de votre application sans avoir à la tester via votre navigateur.

2 Introduction aux tests

Le support des tests a été intégré dans la structure de Rails dès le début. Ce n'était pas une épiphanie du genre "oh ! ajoutons le support des tests car ils sont nouveaux et cool".

2.1 Rails se prépare pour les tests dès le départ

Rails crée un répertoire test pour vous dès que vous créez un projet Rails en utilisant rails new nom_de_l'application. Si vous listez le contenu de ce répertoire, vous verrez :

$ ls -F test
application_system_test_case.rb  controllers/                     helpers/                         mailers/                         system/
channels/                        fixtures/                        integration/                     models/                          test_helper.rb

Les répertoires helpers, mailers et models sont destinés à contenir les tests pour les helpers de vue, les mailers et les modèles, respectivement. Le répertoire channels est destiné à contenir les tests pour les connexions et les canaux Action Cable. Le répertoire controllers est destiné à contenir les tests pour les contrôleurs, les routes et les vues. Le répertoire integration est destiné à contenir les tests pour les interactions entre les contrôleurs.

Le répertoire de tests système contient des tests système, qui sont utilisés pour tester votre application dans un navigateur complet. Les tests système vous permettent de tester votre application de la même manière que vos utilisateurs l'expérimentent et vous aident à tester votre JavaScript également. Les tests système héritent de Capybara et effectuent des tests dans le navigateur pour votre application.

Les fixtures sont une façon d'organiser les données de test ; elles résident dans le répertoire fixtures.

Un répertoire jobs sera également créé lorsqu'un test associé est généré pour la première fois.

Le fichier test_helper.rb contient la configuration par défaut de vos tests.

Le fichier application_system_test_case.rb contient la configuration par défaut de vos tests système.

2.2 L'environnement de test

Par défaut, chaque application Rails dispose de trois environnements : développement, test et production.

La configuration de chaque environnement peut être modifiée de manière similaire. Dans ce cas, nous pouvons modifier notre environnement de test en changeant les options trouvées dans config/environments/test.rb.

NOTE : Vos tests sont exécutés sous RAILS_ENV=test.

2.3 Rails rencontre Minitest

Si vous vous souvenez, nous avons utilisé la commande bin/rails generate model dans le guide Démarrage avec Rails. Nous avons créé notre premier modèle, et entre autres choses, cela a créé des ébauches de tests dans le répertoire test :

$ bin/rails generate model article title:string body:text
...
create  app/models/article.rb
create  test/models/article_test.rb
create  test/fixtures/articles.yml
...

L'ébauche de test par défaut dans test/models/article_test.rb ressemble à ceci :

require "test_helper"

class ArticleTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

Un examen ligne par ligne de ce fichier vous aidera à vous familiariser avec le code et la terminologie des tests Rails.

require "test_helper"

En requérant ce fichier, test_helper.rb, la configuration par défaut pour exécuter nos tests est chargée. Nous l'inclurons avec tous les tests que nous écrirons, de sorte que toutes les méthodes ajoutées à ce fichier soient disponibles pour tous nos tests.

class ArticleTest < ActiveSupport::TestCase

La classe ArticleTest définit un cas de test car elle hérite de ActiveSupport::TestCase. ArticleTest dispose donc de toutes les méthodes disponibles de ActiveSupport::TestCase. Plus tard dans ce guide, nous verrons certaines des méthodes qu'il nous offre.

Toute méthode définie dans une classe héritée de Minitest::Test (qui est la superclasse de ActiveSupport::TestCase) et qui commence par test_ est simplement appelée un test. Ainsi, les méthodes définies comme test_password et test_valid_password sont des noms de test valides et sont exécutées automatiquement lorsque le cas de test est exécuté.

Rails ajoute également une méthode test qui prend un nom de test et un bloc. Elle génère un test normal Minitest::Unit avec des noms de méthode préfixés par test_. Ainsi, vous n'avez pas à vous soucier de nommer les méthodes, et vous pouvez écrire quelque chose comme :

test "the truth" do
  assert true
end

Ce qui est approximativement équivalent à écrire ceci : ruby def test_the_truth assert true end

Bien que vous puissiez toujours utiliser des définitions de méthode régulières, l'utilisation de la macro test permet un nom de test plus lisible.

REMARQUE: Le nom de la méthode est généré en remplaçant les espaces par des tirets bas. Le résultat n'a pas besoin d'être un identifiant Ruby valide, car le nom peut contenir des caractères de ponctuation, etc. En effet, en Ruby, techniquement, n'importe quelle chaîne peut être un nom de méthode. Cela peut nécessiter l'utilisation des appels define_method et send pour fonctionner correctement, mais formellement, il y a peu de restrictions sur le nom.

Ensuite, examinons notre première assertion:

assert true

Une assertion est une ligne de code qui évalue un objet (ou une expression) pour obtenir des résultats attendus. Par exemple, une assertion peut vérifier:

  • est-ce que cette valeur = cette valeur?
  • est-ce que cet objet est nul?
  • cette ligne de code génère-t-elle une exception?
  • le mot de passe de l'utilisateur est-il supérieur à 5 caractères?

Chaque test peut contenir une ou plusieurs assertions, sans restriction quant au nombre d'assertions autorisées. Seulement lorsque toutes les assertions sont réussies, le test réussit.

2.3.1 Votre premier test en échec

Pour voir comment un échec de test est signalé, vous pouvez ajouter un test en échec au cas de test article_test.rb.

test "ne doit pas enregistrer un article sans titre" do
  article = Article.new
  assert_not article.save
end

Exécutons ce nouveau test ajouté (où 6 est le numéro de ligne où le test est défini).

$ bin/rails test test/models/article_test.rb:6
Options d'exécution: --seed 44656

# Exécution:

F

Échec:
ArticleTest#test_ne_doit_pas_enregistrer_un_article_sans_titre [/chemin/vers/blog/test/models/article_test.rb:6]:
Expected true to be nil or false


bin/rails test test/models/article_test.rb:6



Terminé en 0.023918s, 41.8090 exécutions/s, 41.8090 assertions/s.

1 exécutions, 1 assertions, 1 échecs, 0 erreurs, 0 sauts

Dans la sortie, F indique un échec. Vous pouvez voir la trace correspondante affichée sous Échec avec le nom du test en échec. Les lignes suivantes contiennent la trace de la pile suivie d'un message qui mentionne la valeur réelle et la valeur attendue par l'assertion. Les messages d'échec d'assertion par défaut fournissent juste assez d'informations pour aider à localiser l'erreur. Pour rendre le message d'échec d'assertion plus lisible, chaque assertion fournit un paramètre de message facultatif, comme indiqué ici:

test "ne doit pas enregistrer un article sans titre" do
  article = Article.new
  assert_not article.save, "Article enregistré sans titre"
end

L'exécution de ce test affiche le message d'échec d'assertion plus convivial:

Échec:
ArticleTest#test_ne_doit_pas_enregistrer_un_article_sans_titre [/chemin/vers/blog/test/models/article_test.rb:6]:
Article enregistré sans titre

Maintenant, pour faire passer ce test, nous pouvons ajouter une validation au niveau du modèle pour le champ title.

class Article < ApplicationRecord
  validates :title, presence: true
end

Maintenant, le test devrait passer. Vérifions en exécutant à nouveau le test:

$ bin/rails test test/models/article_test.rb:6
Options d'exécution: --seed 31252

# Exécution:

.

Terminé en 0.027476s, 36.3952 exécutions/s, 36.3952 assertions/s.

1 exécutions, 1 assertions, 0 échecs, 0 erreurs, 0 sauts

Maintenant, si vous avez remarqué, nous avons d'abord écrit un test qui échoue pour une fonctionnalité souhaitée, puis nous avons écrit du code qui ajoute la fonctionnalité et enfin nous avons vérifié que notre test passe. Cette approche du développement logiciel est appelée Test-Driven Development (TDD).

2.3.2 À quoi ressemble une erreur

Pour voir comment une erreur est signalée, voici un test contenant une erreur:

test "doit signaler une erreur" do
  # some_undefined_variable n'est pas définie ailleurs dans le cas de test
  some_undefined_variable
  assert true
end

Maintenant, vous pouvez voir encore plus de sortie dans la console lors de l'exécution des tests:

$ bin/rails test test/models/article_test.rb
Options d'exécution: --seed 1808

# Exécution:

.E

Erreur:
ArticleTest#test_doit_signaler_une_erreur:
NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
    test/models/article_test.rb:11:in 'block in <class:ArticleTest>'


bin/rails test test/models/article_test.rb:9



Terminé en 0.040609s, 49.2500 exécutions/s, 24.6250 assertions/s.

2 exécutions, 1 assertions, 0 échecs, 1 erreurs, 0 sauts

Remarquez le 'E' dans la sortie. Il indique un test avec une erreur.

REMARQUE: L'exécution de chaque méthode de test s'arrête dès qu'une erreur ou un échec d'assertion est rencontré, et la suite de tests se poursuit avec la méthode suivante. Toutes les méthodes de test sont exécutées dans un ordre aléatoire. L'option config.active_support.test_order peut être utilisée pour configurer l'ordre des tests.

Lorsqu'un test échoue, vous obtenez la trace correspondante. Par défaut, Rails filtre cette trace et n'affiche que les lignes pertinentes pour votre application. Cela élimine le bruit du framework et permet de se concentrer sur votre code. Cependant, il y a des situations où vous voulez voir la trace complète. Définissez l'argument -b (ou --backtrace) pour activer ce comportement: bash $ bin/rails test -b test/models/article_test.rb

Si nous voulons que ce test réussisse, nous pouvons le modifier pour utiliser assert_raises de la manière suivante :

test "should report error" do
  # some_undefined_variable n'est pas défini ailleurs dans le cas de test
  assert_raises(NameError) do
    some_undefined_variable
  end
end

Ce test devrait maintenant réussir.

2.4 Assertions disponibles

Jusqu'à présent, vous avez eu un aperçu de certaines des assertions disponibles. Les assertions sont les ouvrières des tests. Ce sont elles qui effectuent réellement les vérifications pour s'assurer que tout se passe comme prévu.

Voici un extrait des assertions que vous pouvez utiliser avec Minitest, la bibliothèque de tests par défaut utilisée par Rails. Le paramètre [msg] est une chaîne de message facultative que vous pouvez spécifier pour rendre vos messages d'échec de test plus clairs.

Assertion Objectif
assert( test, [msg] ) Vérifie que test est vrai.
assert_not( test, [msg] ) Vérifie que test est faux.
assert_equal( expected, actual, [msg] ) Vérifie que expected == actual est vrai.
assert_not_equal( expected, actual, [msg] ) Vérifie que expected != actual est vrai.
assert_same( expected, actual, [msg] ) Vérifie que expected.equal?(actual) est vrai.
assert_not_same( expected, actual, [msg] ) Vérifie que expected.equal?(actual) est faux.
assert_nil( obj, [msg] ) Vérifie que obj.nil? est vrai.
assert_not_nil( obj, [msg] ) Vérifie que obj.nil? est faux.
assert_empty( obj, [msg] ) Vérifie que obj est empty?.
assert_not_empty( obj, [msg] ) Vérifie que obj n'est pas empty?.
assert_match( regexp, string, [msg] ) Vérifie qu'une chaîne correspond à l'expression régulière.
assert_no_match( regexp, string, [msg] ) Vérifie qu'une chaîne ne correspond pas à l'expression régulière.
assert_includes( collection, obj, [msg] ) Vérifie que obj est dans collection.
assert_not_includes( collection, obj, [msg] ) Vérifie que obj n'est pas dans collection.
assert_in_delta( expected, actual, [delta], [msg] ) Vérifie que les nombres expected et actual sont à delta près l'un de l'autre.
assert_not_in_delta( expected, actual, [delta], [msg] ) Vérifie que les nombres expected et actual ne sont pas à delta près l'un de l'autre.
assert_in_epsilon ( expected, actual, [epsilon], [msg] ) Vérifie que les nombres expected et actual ont une erreur relative inférieure à epsilon.
assert_not_in_epsilon ( expected, actual, [epsilon], [msg] ) Vérifie que les nombres expected et actual ont une erreur relative supérieure à epsilon.
assert_throws( symbol, [msg] ) { block } Vérifie que le bloc donné lance le symbole.
assert_raises( exception1, exception2, ... ) { block } Vérifie que le bloc donné lève l'une des exceptions données.
assert_instance_of( class, obj, [msg] ) Vérifie que obj est une instance de class.
assert_not_instance_of( class, obj, [msg] ) Vérifie que obj n'est pas une instance de class.
assert_kind_of( class, obj, [msg] ) Vérifie que obj est une instance de class ou en descend.
assert_not_kind_of( class, obj, [msg] ) Vérifie que obj n'est pas une instance de class et n'en descend pas.
assert_respond_to( obj, symbol, [msg] ) Vérifie que obj répond à symbol.
assert_not_respond_to( obj, symbol, [msg] ) Vérifie que obj ne répond pas à symbol.
assert_operator( obj1, operator, [obj2], [msg] ) Vérifie que obj1.operator(obj2) est vrai.
assert_not_operator( obj1, operator, [obj2], [msg] ) Vérifie que obj1.operator(obj2) est faux.
assert_predicate ( obj, predicate, [msg] ) Vérifie que obj.predicate est vrai, par exemple assert_predicate str, :empty?
assert_not_predicate ( obj, predicate, [msg] ) Vérifie que obj.predicate est faux, par exemple assert_not_predicate str, :empty?
flunk( [msg] ) Force l'échec. Cela est utile pour marquer explicitement un test qui n'est pas encore terminé.

Ce qui précède est un sous-ensemble des assertions que minitest prend en charge. Pour une liste exhaustive et plus à jour, veuillez consulter la documentation de l'API de Minitest, en particulier Minitest::Assertions.

En raison de la nature modulaire du framework de test, il est possible de créer vos propres assertions. En fait, c'est exactement ce que fait Rails. Il inclut certaines assertions spécialisées pour vous faciliter la vie.

REMARQUE : La création de vos propres assertions est un sujet avancé que nous n'aborderons pas dans ce tutoriel.

2.5 Assertions spécifiques à Rails

Rails ajoute ses propres assertions personnalisées au framework minitest : | Assertion | But | | --------------------------------------------------------------------------------- | ------- | | assert_difference(expressions, difference = 1, message = nil) {...} | Teste la différence numérique entre la valeur de retour d'une expression en conséquence de ce qui est évalué dans le bloc donné.| | assert_no_difference(expressions, message = nil, &block) | Vérifie que le résultat numérique de l'évaluation d'une expression n'est pas modifié avant et après l'invocation du bloc donné.| | assert_changes(expressions, message = nil, from:, to:, &block) | Teste que le résultat de l'évaluation d'une expression est modifié après l'invocation du bloc donné.| | assert_no_changes(expressions, message = nil, &block) | Teste que le résultat de l'évaluation d'une expression n'est pas modifié après l'invocation du bloc donné.| | assert_nothing_raised { block } | S'assure que le bloc donné ne génère aucune exception.| | assert_recognizes(expected_options, path, extras={}, message=nil) | Vérifie que le routage du chemin donné a été géré correctement et que les options analysées (données dans le hachage expected_options) correspondent au chemin. Fondamentalement, cela vérifie que Rails reconnaît la route donnée par expected_options.| | assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) | Vérifie que les options fournies peuvent être utilisées pour générer le chemin fourni. C'est l'inverse de assert_recognizes. Le paramètre extras est utilisé pour indiquer à la requête les noms et valeurs des paramètres de requête supplémentaires qui seraient dans une chaîne de requête. Le paramètre message vous permet de spécifier un message d'erreur personnalisé pour les échecs de l'assertion.| | assert_response(type, message = nil) | Vérifie que la réponse est renvoyée avec un code d'état spécifique. Vous pouvez spécifier :success pour indiquer 200-299, :redirect pour indiquer 300-399, :missing pour indiquer 404, ou :error pour correspondre à la plage 500-599. Vous pouvez également passer un numéro de statut explicite ou son équivalent symbolique. Pour plus d'informations, consultez la liste complète des codes de statut et comment fonctionne leur correspondance.| | assert_redirected_to(options = {}, message=nil) | Vérifie que la réponse est une redirection vers une URL correspondant aux options données. Vous pouvez également passer des routes nommées telles que assert_redirected_to root_path et des objets Active Record tels que assert_redirected_to @article.|

Vous verrez l'utilisation de certaines de ces assertions dans le prochain chapitre.

2.6 Une brève note sur les cas de test

Toutes les assertions de base telles que assert_equal définies dans Minitest::Assertions sont également disponibles dans les classes que nous utilisons dans nos propres cas de test. En fait, Rails fournit les classes suivantes à partir desquelles vous pouvez hériter :

Chacune de ces classes inclut Minitest::Assertions, ce qui nous permet d'utiliser toutes les assertions de base dans nos tests.

NOTE : Pour plus d'informations sur Minitest, consultez sa documentation.

2.7 L'exécuteur de tests Rails

Nous pouvons exécuter tous nos tests en une seule fois en utilisant la commande bin/rails test.

Ou nous pouvons exécuter un seul fichier de test en passant à la commande bin/rails test le nom du fichier contenant les cas de test.

$ bin/rails test test/models/article_test.rb
Options d'exécution : --seed 1559

# Exécution :

..

Terminé en 0,027034s, 73,9810 exécutions/s, 110,9715 assertions/s.

2 exécutions, 3 assertions, 0 échecs, 0 erreurs, 0 sauts

Cela exécutera toutes les méthodes de test du cas de test.

Vous pouvez également exécuter une méthode de test particulière du cas de test en fournissant le drapeau -n ou --name et le nom de la méthode de test.

$ bin/rails test test/models/article_test.rb -n test_the_truth
Options d'exécution : -n test_the_truth --seed 43583

# Exécution :

.

Tests terminés en 0,009064s, 110,3266 tests/s, 110,3266 assertions/s.

1 test, 1 assertion, 0 échecs, 0 erreurs, 0 sauts

Vous pouvez également exécuter un test à une ligne spécifique en fournissant le numéro de ligne.

$ bin/rails test test/models/article_test.rb:6 # exécuter un test spécifique et une ligne

Vous pouvez également exécuter un répertoire entier de tests en fournissant le chemin du répertoire.

$ bin/rails test test/controllers # exécuter tous les tests d'un répertoire spécifique

L'exécuteur de tests offre également de nombreuses autres fonctionnalités telles que l'arrêt rapide en cas d'échec, le report différé des résultats des tests à la fin de l'exécution des tests, etc. Consultez la documentation de l'exécuteur de tests comme suit :

$ bin/rails test -h
Utilisation : rails test [options] [fichiers ou répertoires]

Vous pouvez exécuter un seul test en ajoutant un numéro de ligne à un nom de fichier :

    bin/rails test test/models/user_test.rb:27

Vous pouvez exécuter plusieurs fichiers et répertoires en même temps :

    bin/rails test test/controllers test/integration/login_test.rb

Par défaut, les échecs et les erreurs des tests sont signalés en ligne pendant l'exécution.

Options de minitest :
    -h, --help                       Affiche cette aide.
        --no-plugins                 Contourne le chargement automatique des plugins minitest (ou définissez $MT_NO_PLUGINS).
    -s, --seed SEED                  Définit la graine aléatoire. Également via env. Ex : SEED=n rake
    -v, --verbose                    Verbose. Affiche la progression du traitement des fichiers.
    -n, --name PATTERN               Filtre l'exécution sur /regexp/ ou une chaîne.
        --exclude PATTERN            Exclut /regexp/ ou une chaîne de l'exécution.

Extensions connues : rails, pride
    -w, --warnings                   Exécute avec les avertissements Ruby activés
    -e, --environment ENV            Exécute les tests dans l'environnement ENV
    -b, --backtrace                  Affiche la trace complète
    -d, --defer-output               Affiche les échecs et les erreurs des tests après l'exécution des tests
    -f, --fail-fast                  Interrompt l'exécution des tests au premier échec ou erreur
    -c, --[no-]color                 Active la couleur dans la sortie
    -p, --pride                      Fierté. Montre ta fierté de tester !

2.8 Exécution des tests en intégration continue (CI)

Pour exécuter tous les tests dans un environnement CI, il vous suffit d'une seule commande :

$ bin/rails test

Si vous utilisez des tests système, bin/rails test ne les exécutera pas, car ils peuvent être lents. Pour les exécuter également, ajoutez une autre étape CI qui exécute bin/rails test:system, ou modifiez votre première étape en bin/rails test:all, qui exécute tous les tests, y compris les tests système.

3 Tests parallèles

Les tests parallèles vous permettent de paralléliser votre suite de tests. Bien que la méthode par défaut soit la création de processus, le threading est également pris en charge. L'exécution de tests en parallèle réduit le temps nécessaire pour exécuter l'ensemble de votre suite de tests.

3.1 Tests parallèles avec des processus

La méthode de parallélisation par défaut consiste à créer des processus en utilisant le système DRb de Ruby. Les processus sont créés en fonction du nombre de travailleurs fournis. Le nombre par défaut est le nombre réel de cœurs sur la machine sur laquelle vous vous trouvez, mais peut être modifié en passant le nombre à la méthode parallelize.

Pour activer la parallélisation, ajoutez ce qui suit à votre test_helper.rb :

class ActiveSupport::TestCase
  parallelize(workers: 2)
end

Le nombre de travailleurs passés est le nombre de fois que le processus sera créé. Vous voudrez peut-être paralléliser votre suite de tests locale différemment de votre CI, donc une variable d'environnement est fournie pour pouvoir facilement changer le nombre de travailleurs qu'une exécution de test doit utiliser :

$ PARALLEL_WORKERS=15 bin/rails test

Lors de la parallélisation des tests, Active Record gère automatiquement la création d'une base de données et le chargement du schéma dans la base de données pour chaque processus. Les bases de données seront suffixées par le numéro correspondant au travailleur. Par exemple, si vous avez 2 travailleurs, les tests créeront respectivement test-database-0 et test-database-1.

Si le nombre de travailleurs passés est inférieur ou égal à 1, les processus ne seront pas créés et les tests ne seront pas parallélisés et les tests utiliseront la base de données test-database d'origine.

Deux hooks sont fournis, l'un s'exécute lorsque le processus est créé, et l'autre s'exécute avant que le processus créé ne soit fermé. Ils peuvent être utiles si votre application utilise plusieurs bases de données ou effectue d'autres tâches qui dépendent du nombre de travailleurs.

La méthode parallelize_setup est appelée juste après la création des processus. La méthode parallelize_teardown est appelée juste avant la fermeture des processus.

class ActiveSupport::TestCase
  parallelize_setup do |worker|
    # configuration des bases de données
  end

  parallelize_teardown do |worker|
    # nettoyage des bases de données
  end

  parallelize(workers: :number_of_processors)
end

Ces méthodes ne sont pas nécessaires ou disponibles lors de l'utilisation de tests parallèles avec des threads.

3.2 Tests parallèles avec des threads

Si vous préférez utiliser des threads ou si vous utilisez JRuby, une option de parallélisation par thread est disponible. Le paralléliseur threadé est basé sur l'exécuteur Parallel::Executor de Minitest.

Pour changer la méthode de parallélisation pour utiliser des threads plutôt que des processus, ajoutez ce qui suit à votre test_helper.rb :

class ActiveSupport::TestCase
  parallelize(workers: :number_of_processors, with: :threads)
end

Les applications Rails générées à partir de JRuby ou TruffleRuby incluront automatiquement l'option with: :threads.

Le nombre de travailleurs passé à parallelize détermine le nombre de threads que les tests utiliseront. Vous voudrez peut-être paralléliser votre suite de tests locale différemment de votre CI, donc une variable d'environnement est fournie pour pouvoir facilement changer le nombre de travailleurs qu'une exécution de test doit utiliser :

$ PARALLEL_WORKERS=15 bin/rails test

3.3 Tests de transactions parallèles

Rails enveloppe automatiquement chaque cas de test dans une transaction de base de données qui est annulée après l'exécution du test. Cela rend les cas de test indépendants les uns des autres et les modifications apportées à la base de données ne sont visibles que dans un seul test.

Lorsque vous souhaitez tester du code qui exécute des transactions parallèles dans des threads, les transactions peuvent se bloquer mutuellement car elles sont déjà imbriquées sous la transaction de test.

Vous pouvez désactiver les transactions dans une classe de cas de test en définissant self.use_transactional_tests = false :

class WorkerTest < ActiveSupport::TestCase
  self.use_transactional_tests = false

  test "transactions parallèles" do
    # démarrer des threads qui créent des transactions
  end
end

REMARQUE : Avec les tests transactionnels désactivés, vous devez nettoyer toutes les données créées par les tests, car les modifications ne sont pas automatiquement annulées après l'exécution du test.

3.4 Seuil de parallélisation des tests

L'exécution de tests en parallèle ajoute une surcharge en termes de configuration de la base de données et de chargement des fixtures. Pour cette raison, Rails ne parallélisera pas les exécutions qui impliquent moins de 50 tests.

Vous pouvez configurer ce seuil dans votre test.rb : ruby config.active_support.test_parallelization_threshold = 100

Et aussi lors de la configuration de la parallélisation au niveau des cas de test :

class ActiveSupport::TestCase
  parallelize threshold: 100
end

4 La base de données de test

Presque toutes les applications Rails interagissent intensivement avec une base de données et, par conséquent, vos tests auront également besoin d'une base de données avec laquelle interagir. Pour écrire des tests efficaces, vous devrez comprendre comment configurer cette base de données et la remplir avec des données d'exemple.

Par défaut, chaque application Rails dispose de trois environnements : développement, test et production. La base de données de chacun d'entre eux est configurée dans config/database.yml.

Une base de données de test dédiée vous permet de configurer et d'interagir avec des données de test de manière isolée. De cette façon, vos tests peuvent manipuler les données de test en toute confiance, sans se soucier des données dans les bases de données de développement ou de production.

4.1 Maintenir le schéma de la base de données de test

Pour exécuter vos tests, votre base de données de test devra avoir la structure actuelle. L'assistant de test vérifie si votre base de données de test a des migrations en attente. Il essaiera de charger votre db/schema.rb ou db/structure.sql dans la base de données de test. Si des migrations sont encore en attente, une erreur sera générée. Cela indique généralement que votre schéma n'est pas entièrement migré. Exécuter les migrations contre la base de données de développement (bin/rails db:migrate) mettra à jour le schéma.

NOTE : Si des modifications ont été apportées aux migrations existantes, la base de données de test doit être reconstruite. Cela peut être fait en exécutant bin/rails db:test:prepare.

4.2 Les bases des données de test

Pour de bons tests, vous devrez réfléchir à la configuration des données de test. Dans Rails, vous pouvez le faire en définissant et en personnalisant des bases de données de test. Vous pouvez trouver une documentation complète dans la documentation de l'API des bases de données de test.

4.2.1 Qu'est-ce qu'une base de données de test ?

Les bases de données de test est un terme élégant pour désigner des données d'exemple. Les bases de données de test vous permettent de remplir votre base de données de test avec des données prédéfinies avant l'exécution de vos tests. Les bases de données de test sont indépendantes de la base de données et sont écrites en YAML. Il y a un fichier par modèle.

NOTE : Les bases de données de test ne sont pas conçues pour créer tous les objets dont vos tests ont besoin, et elles sont mieux gérées lorsqu'elles sont utilisées uniquement pour les données par défaut qui peuvent être appliquées au cas général.

Vous trouverez les bases de données de test dans votre répertoire test/fixtures. Lorsque vous exécutez bin/rails generate model pour créer un nouveau modèle, Rails crée automatiquement des ébauches de bases de données de test dans ce répertoire.

4.2.2 YAML

Les bases de données de test au format YAML sont une façon conviviale de décrire vos données d'exemple. Ces types de bases de données de test ont l'extension de fichier .yml (comme users.yml).

Voici un exemple de fichier de base de données de test YAML :

# lo & behold! I am a YAML comment!
david:
  name: David Heinemeier Hansson
  birthday: 1979-10-15
  profession: Développement de systèmes

steve:
  name: Steve Ross Kellock
  birthday: 1974-09-27
  profession: gars avec un clavier

Chaque base de données de test est donnée un nom suivi d'une liste indentée de paires clé/valeur séparées par des deux-points. Les enregistrements sont généralement séparés par une ligne vide. Vous pouvez placer des commentaires dans un fichier de base de données de test en utilisant le caractère # en première colonne.

Si vous travaillez avec des associations, vous pouvez définir un nœud de référence entre deux bases de données de test différentes. Voici un exemple avec une association belongs_to/has_many :

# test/fixtures/categories.yml
about:
  name: À propos
# test/fixtures/articles.yml
first:
  title: Bienvenue dans Rails !
  category: about
# test/fixtures/action_text/rich_texts.yml
first_content:
  record: first (Article)
  name: content
  body: <div>Bonjour, depuis <strong>une base de données de test</strong></div>

Remarquez que la clé category de l'article first trouvé dans fixtures/articles.yml a une valeur de about, et que la clé record de l'entrée first_content trouvée dans fixtures/action_text/rich_texts.yml a une valeur de first (Article). Cela indique à Active Record de charger la catégorie about trouvée dans fixtures/categories.yml pour le premier, et à Action Text de charger l'article first trouvé dans fixtures/articles.yml pour le second.

NOTE : Pour que les associations se réfèrent les unes aux autres par leur nom, vous pouvez utiliser le nom de la base de données de test à la place de spécifier l'attribut id: sur les bases de données de test associées. Rails attribuera automatiquement une clé primaire pour être cohérente entre les exécutions. Pour plus d'informations sur ce comportement d'association, veuillez lire la documentation de l'API des bases de données de test.

4.2.3 Bases de données de test pour les pièces jointes de fichiers

Comme les autres modèles basés sur Active Record, les enregistrements de pièces jointes Active Storage héritent des instances de ActiveRecord::Base et peuvent donc être remplis par des bases de données de test.

Considérez un modèle Article qui a une image associée en tant que pièce jointe thumbnail, ainsi que des données de base de données de test YAML :

class Article
  has_one_attached :thumbnail
end
# test/fixtures/articles.yml
first:
  title: Un article

En supposant qu'il existe un fichier encodé image/png à l'emplacement test/fixtures/files/first.png, les entrées de fixture YAML suivantes généreront les enregistrements ActiveStorage::Blob et ActiveStorage::Attachment associés :

# test/fixtures/active_storage/blobs.yml
first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
# test/fixtures/active_storage/attachments.yml
first_thumbnail_attachment:
  name: thumbnail
  record: first (Article)
  blob: first_thumbnail_blob

4.2.4 ERB'in It Up

ERB vous permet d'intégrer du code Ruby dans les modèles. Le format de fixture YAML est prétraité avec ERB lorsque Rails charge les fixtures. Cela vous permet d'utiliser Ruby pour vous aider à générer des données d'exemple. Par exemple, le code suivant génère mille utilisateurs :

<% 1000.times do |n| %>
user_<%= n %>:
  username: <%= "user#{n}" %>
  email: <%= "user#{n}@example.com" %>
<% end %>

4.2.5 Les fixtures en action

Rails charge automatiquement toutes les fixtures du répertoire test/fixtures par défaut. Le chargement se fait en trois étapes :

  1. Supprimer toutes les données existantes de la table correspondant à la fixture
  2. Charger les données de la fixture dans la table
  3. Sauvegarder les données de la fixture dans une méthode au cas où vous souhaitez y accéder directement

CONSEIL : Pour supprimer les données existantes de la base de données, Rails essaie de désactiver les déclencheurs d'intégrité référentielle (comme les clés étrangères et les contraintes de vérification). Si vous rencontrez des erreurs de permission ennuyeuses lors de l'exécution des tests, assurez-vous que l'utilisateur de la base de données a le privilège de désactiver ces déclencheurs dans l'environnement de test. (Dans PostgreSQL, seuls les superutilisateurs peuvent désactiver tous les déclencheurs. En savoir plus sur les autorisations PostgreSQL ici).

4.2.6 Les fixtures sont des objets Active Record

Les fixtures sont des instances d'Active Record. Comme mentionné au point #3 ci-dessus, vous pouvez accéder à l'objet directement car il est automatiquement disponible en tant que méthode dont la portée est locale au cas de test. Par exemple :

# cela renverra l'objet User pour la fixture nommée david
users(:david)

# cela renverra la propriété id de david
users(:david).id

# on peut également accéder aux méthodes disponibles sur la classe User
david = users(:david)
david.call(david.partner)

Pour obtenir plusieurs fixtures à la fois, vous pouvez passer une liste de noms de fixtures. Par exemple :

# cela renverra un tableau contenant les fixtures david et steve
users(:david, :steve)

5 Test des modèles

Les tests des modèles sont utilisés pour tester les différents modèles de votre application.

Les tests des modèles Rails sont stockés dans le répertoire test/models. Rails fournit un générateur pour créer un squelette de test de modèle pour vous.

$ bin/rails generate test_unit:model article title:string body:text
create  test/models/article_test.rb
create  test/fixtures/articles.yml

Les tests des modèles n'ont pas de superclasse propre comme ActionMailer::TestCase. Au lieu de cela, ils héritent de ActiveSupport::TestCase.

6 Test du système

Les tests du système vous permettent de tester les interactions des utilisateurs avec votre application, en exécutant des tests dans un navigateur réel ou sans tête. Les tests du système utilisent Capybara en interne.

Pour créer des tests du système Rails, utilisez le répertoire test/system de votre application. Rails fournit un générateur pour créer un squelette de test du système pour vous.

$ bin/rails generate system_test users
      invoke test_unit
      create test/system/users_test.rb

Voici à quoi ressemble un test du système fraîchement généré :

require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  # test "visiting the index" do
  #   visit users_url
  #
  #   assert_selector "h1", text: "Users"
  # end
end

Par défaut, les tests du système sont exécutés avec le pilote Selenium, en utilisant le navigateur Chrome et une taille d'écran de 1400x1400. La section suivante explique comment modifier les paramètres par défaut.

6.1 Modification des paramètres par défaut

Rails facilite grandement la modification des paramètres par défaut des tests du système. Toute la configuration est abstraite pour que vous puissiez vous concentrer sur l'écriture de vos tests.

Lorsque vous générez une nouvelle application ou un nouveau scaffold, un fichier application_system_test_case.rb est créé dans le répertoire de test. C'est là que toute la configuration de vos tests du système doit être placée.

Si vous souhaitez modifier les paramètres par défaut, vous pouvez modifier ce qui "pilote" les tests du système. Disons que vous voulez changer le pilote de Selenium à Cuprite. Ajoutez d'abord la gem cuprite à votre Gemfile. Ensuite, dans votre fichier application_system_test_case.rb, faites ce qui suit :

require "test_helper"
require "capybara/cuprite"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :cuprite
end

Le nom du pilote est un argument obligatoire pour driven_by. Les arguments facultatifs qui peuvent être passés à driven_by sont :using pour le navigateur (cela ne sera utilisé que par Selenium), :screen_size pour changer la taille de l'écran pour les captures d'écran, et :options qui peuvent être utilisées pour définir des options prises en charge par le pilote. ```ruby require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :firefox end ```

Si vous souhaitez utiliser un navigateur sans interface graphique, vous pouvez utiliser Headless Chrome ou Headless Firefox en ajoutant headless_chrome ou headless_firefox dans l'argument :using.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome
end

Si vous souhaitez utiliser un navigateur distant, par exemple Headless Chrome dans Docker, vous devez ajouter l'URL distante via les options.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  options = ENV["SELENIUM_REMOTE_URL"].present? ? { url: ENV["SELENIUM_REMOTE_URL"] } : {}
  driven_by :selenium, using: :headless_chrome, options: options
end

Dans ce cas, la gem webdrivers n'est plus nécessaire. Vous pouvez la supprimer complètement ou ajouter l'option require: dans le fichier Gemfile.

# ...
group :test do
  gem "webdrivers", require: !ENV["SELENIUM_REMOTE_URL"] || ENV["SELENIUM_REMOTE_URL"].empty?
end

Maintenant, vous devriez obtenir une connexion au navigateur distant.

$ SELENIUM_REMOTE_URL=http://localhost:4444/wd/hub bin/rails test:system

Si votre application en test s'exécute également à distance, par exemple dans un conteneur Docker, Capybara a besoin de plus d'informations sur la façon d'appeler les serveurs distants.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  def setup
    Capybara.server_host = "0.0.0.0" # lier à toutes les interfaces
    Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}" if ENV["SELENIUM_REMOTE_URL"].present?
    super
  end
  # ...
end

Maintenant, vous devriez obtenir une connexion au navigateur et au serveur distant, que ce soit dans un conteneur Docker ou dans un environnement d'intégration continue.

Si votre configuration Capybara nécessite plus de paramètres que ceux fournis par Rails, cette configuration supplémentaire peut être ajoutée dans le fichier application_system_test_case.rb.

Veuillez consulter la documentation de Capybara pour des paramètres supplémentaires.

6.2 Aide pour les captures d'écran

Le ScreenshotHelper est un assistant conçu pour capturer des captures d'écran de vos tests. Cela peut être utile pour visualiser le navigateur à l'endroit où un test a échoué, ou pour visualiser ultérieurement les captures d'écran à des fins de débogage.

Deux méthodes sont fournies : take_screenshot et take_failed_screenshot. take_failed_screenshot est automatiquement inclus dans before_teardown à l'intérieur de Rails.

La méthode d'aide take_screenshot peut être incluse n'importe où dans vos tests pour prendre une capture d'écran du navigateur.

6.3 Mise en œuvre d'un test système

Maintenant, nous allons ajouter un test système à notre application de blog. Nous allons démontrer l'écriture d'un test système en visitant la page d'index et en créant un nouvel article de blog.

Si vous avez utilisé le générateur de squelette, un squelette de test système a été automatiquement créé pour vous. Si vous n'avez pas utilisé le générateur de squelette, commencez par créer un squelette de test système.

$ bin/rails generate system_test articles

Il devrait avoir créé un fichier de test fictif pour nous. Avec la sortie de la commande précédente, vous devriez voir :

      invoke  test_unit
      create    test/system/articles_test.rb

Maintenant, ouvrons ce fichier et écrivons notre première assertion :

require "application_system_test_case"

class ArticlesTest < ApplicationSystemTestCase
  test "viewing the index" do
    visit articles_path
    assert_selector "h1", text: "Articles"
  end
end

Le test devrait vérifier qu'il y a un h1 sur la page d'index des articles et réussir.

Exécutez les tests système.

$ bin/rails test:system

NOTE : Par défaut, l'exécution de bin/rails test ne lancera pas vos tests système. Assurez-vous d'exécuter bin/rails test:system pour les exécuter réellement. Vous pouvez également exécuter bin/rails test:all pour exécuter tous les tests, y compris les tests système.

6.3.1 Création d'un test système pour les articles

Maintenant, testons le flux de création d'un nouvel article dans notre blog.

test "should create Article" do
  visit articles_path

  click_on "New Article"

  fill_in "Title", with: "Creating an Article"
  fill_in "Body", with: "Created this article successfully!"

  click_on "Create Article"

  assert_text "Creating an Article"
end

La première étape consiste à appeler visit articles_path. Cela amènera le test à la page d'index des articles.

Ensuite, click_on "New Article" trouvera le bouton "New Article" sur la page d'index. Cela redirigera le navigateur vers /articles/new.

Ensuite, le test remplira le titre et le corps de l'article avec le texte spécifié. Une fois les champs remplis, "Create Article" est cliqué, ce qui enverra une requête POST pour créer le nouvel article dans la base de données.

Nous serons redirigés vers la page d'index des articles et nous vérifions que le texte du titre du nouvel article est présent sur la page d'index des articles.

6.3.2 Test pour plusieurs tailles d'écran

Si vous souhaitez tester les tailles mobiles en plus des tailles de bureau, vous pouvez créer une autre classe qui hérite de ActionDispatch::SystemTestCase et l'utiliser dans votre suite de tests. Dans cet exemple, un fichier appelé mobile_system_test_case.rb est créé dans le répertoire /test avec la configuration suivante. ```ruby require "test_helper"

class MobileSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [375, 667] end ```

Pour utiliser cette configuration, créez un test à l'intérieur de test/system qui hérite de MobileSystemTestCase. Maintenant, vous pouvez tester votre application en utilisant plusieurs configurations différentes.

require "mobile_system_test_case"

class PostsTest < MobileSystemTestCase
  test "visiting the index" do
    visit posts_url
    assert_selector "h1", text: "Posts"
  end
end

6.3.3 Aller plus loin

La beauté des tests système est qu'ils sont similaires aux tests d'intégration dans la mesure où ils testent l'interaction de l'utilisateur avec votre contrôleur, votre modèle et votre vue, mais les tests système sont beaucoup plus robustes et testent réellement votre application comme si un véritable utilisateur l'utilisait. À l'avenir, vous pouvez tester tout ce que l'utilisateur ferait dans votre application, comme commenter, supprimer des articles, publier des articles en brouillon, etc.

7 Tests d'intégration

Les tests d'intégration sont utilisés pour tester comment différentes parties de notre application interagissent. Ils sont généralement utilisés pour tester les flux de travail importants au sein de notre application.

Pour créer des tests d'intégration Rails, nous utilisons le répertoire test/integration de notre application. Rails fournit un générateur pour créer un squelette de test d'intégration.

$ bin/rails generate integration_test user_flows
      exists  test/integration/
      create  test/integration/user_flows_test.rb

Voici à quoi ressemble un test d'intégration nouvellement généré :

require "test_helper"

class UserFlowsTest < ActionDispatch::IntegrationTest
  # test "the truth" do
  #   assert true
  # end
end

Ici, le test hérite de ActionDispatch::IntegrationTest. Cela rend certains assistants supplémentaires disponibles pour nous aider dans nos tests d'intégration.

7.1 Assistants disponibles pour les tests d'intégration

En plus des assistants de test standard, l'héritage de ActionDispatch::IntegrationTest offre quelques assistants supplémentaires disponibles lors de l'écriture de tests d'intégration. Faisons une brève présentation des trois catégories d'assistants que nous pouvons choisir.

Pour gérer l'exécution des tests d'intégration, consultez ActionDispatch::Integration::Runner.

Lors de l'exécution de requêtes, nous aurons ActionDispatch::Integration::RequestHelpers à notre disposition.

Si nous devons modifier la session ou l'état de notre test d'intégration, consultez ActionDispatch::Integration::Session pour obtenir de l'aide.

7.2 Mise en œuvre d'un test d'intégration

Ajoutons un test d'intégration à notre application de blog. Nous commencerons par un flux de travail de base consistant à créer un nouvel article de blog pour vérifier que tout fonctionne correctement.

Commençons par générer le squelette de notre test d'intégration :

$ bin/rails generate integration_test blog_flow

Cela devrait avoir créé un fichier de test fictif pour nous. Avec la sortie de la commande précédente, nous devrions voir :

      invoke  test_unit
      create    test/integration/blog_flow_test.rb

Ouvrons maintenant ce fichier et écrivons notre première assertion :

require "test_helper"

class BlogFlowTest < ActionDispatch::IntegrationTest
  test "can see the welcome page" do
    get "/"
    assert_select "h1", "Welcome#index"
  end
end

Nous examinerons assert_select pour interroger le HTML résultant d'une requête dans la section "Testing Views" ci-dessous. Il est utilisé pour tester la réponse de notre requête en vérifiant la présence d'éléments HTML clés et de leur contenu.

Lorsque nous visitons notre chemin racine, nous devrions voir welcome/index.html.erb rendu pour la vue. Donc cette assertion devrait passer.

7.2.1 Création d'une intégration d'articles

Et si nous testions notre capacité à créer un nouvel article dans notre blog et à voir l'article résultant.

test "can create an article" do
  get "/articles/new"
  assert_response :success

  post "/articles",
    params: { article: { title: "can create", body: "article successfully." } }
  assert_response :redirect
  follow_redirect!
  assert_response :success
  assert_select "p", "Title:\n  can create"
end

Décortiquons ce test pour le comprendre.

Nous commençons par appeler l'action :new sur notre contrôleur Articles. Cette réponse devrait être réussie.

Ensuite, nous effectuons une requête POST vers l'action :create de notre contrôleur Articles :

post "/articles",
  params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!

Les deux lignes suivant la requête servent à gérer la redirection que nous avons configurée lors de la création d'un nouvel article.

NOTE : N'oubliez pas d'appeler follow_redirect! si vous prévoyez de faire des requêtes ultérieures après une redirection.

Enfin, nous pouvons affirmer que notre réponse a été réussie et que notre nouvel article est lisible sur la page.

7.2.2 Aller plus loin

Nous avons réussi à tester avec succès un flux de travail très simple pour visiter notre blog et créer un nouvel article. Si nous voulions aller plus loin, nous pourrions ajouter des tests pour les commentaires, la suppression d'articles ou la modification de commentaires. Les tests d'intégration sont un excellent moyen d'expérimenter toutes sortes de cas d'utilisation pour nos applications.

8 Tests fonctionnels pour vos contrôleurs

Dans Rails, tester les différentes actions d'un contrôleur est une forme de tests fonctionnels. Rappelez-vous que vos contrôleurs gèrent les requêtes web entrantes de votre application et finissent par répondre avec une vue rendue. Lorsque vous écrivez des tests fonctionnels, vous testez comment vos actions gèrent les requêtes et le résultat ou la réponse attendue, dans certains cas une vue HTML.

8.1 Ce qu'il faut inclure dans vos tests fonctionnels

Vous devriez tester des choses telles que :

  • la requête web a-t-elle réussi ?
  • l'utilisateur a-t-il été redirigé vers la bonne page ?
  • l'authentification de l'utilisateur a-t-elle réussi ?
  • le message approprié a-t-il été affiché à l'utilisateur dans la vue ?
  • les informations correctes ont-elles été affichées dans la réponse ?

La manière la plus simple de voir des tests fonctionnels en action est de générer un contrôleur en utilisant le générateur de squelette :

$ bin/rails generate scaffold_controller article title:string body:text
...
create  app/controllers/articles_controller.rb
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

Cela générera le code du contrôleur et les tests pour une ressource Article. Vous pouvez consulter le fichier articles_controller_test.rb dans le répertoire test/controllers.

Si vous avez déjà un contrôleur et que vous voulez simplement générer le code de squelette de test pour chacune des sept actions par défaut, vous pouvez utiliser la commande suivante :

$ bin/rails generate test_unit:scaffold article
...
invoke  test_unit
create    test/controllers/articles_controller_test.rb
...

Jetons un coup d'œil à l'un de ces tests, test_should_get_index du fichier articles_controller_test.rb.

# articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_url
    assert_response :success
  end
end

Dans le test test_should_get_index, Rails simule une requête sur l'action appelée index, en s'assurant que la requête a réussi et en vérifiant également que le bon corps de réponse a été généré.

La méthode get lance la requête web et remplit les résultats dans @response. Elle peut accepter jusqu'à 6 arguments :

  • L'URI de l'action du contrôleur que vous demandez. Cela peut être sous forme de chaîne de caractères ou d'un helper de route (par exemple articles_url).
  • params : option avec un hash de paramètres de requête à passer à l'action (par exemple des paramètres de chaîne de requête ou des variables d'article).
  • headers : pour définir les en-têtes qui seront transmis avec la requête.
  • env : pour personnaliser l'environnement de la requête si nécessaire.
  • xhr : indique si la requête est une requête Ajax ou non. Peut être défini sur true pour marquer la requête comme Ajax.
  • as : pour encoder la requête avec un type de contenu différent.

Tous ces arguments de mot-clé sont facultatifs.

Exemple : Appeler l'action :show pour le premier Article, en passant un en-tête HTTP_REFERER :

get article_url(Article.first), headers: { "HTTP_REFERER" => "http://example.com/home" }

Autre exemple : Appeler l'action :update pour le dernier Article, en passant un nouveau texte pour le title dans params, en tant que requête Ajax :

patch article_url(Article.last), params: { article: { title: "updated" } }, xhr: true

Encore un exemple : Appeler l'action :create pour créer un nouvel article, en passant du texte pour le title dans params, en tant que requête JSON :

post articles_path, params: { article: { title: "Ahoy!" } }, as: :json

NOTE : Si vous essayez d'exécuter le test test_should_create_article de articles_controller_test.rb, il échouera en raison de la validation ajoutée au niveau du modèle et c'est normal.

Modifions le test test_should_create_article dans articles_controller_test.rb pour que tous nos tests passent :

test "should create article" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }
  end

  assert_redirected_to article_path(Article.last)
end

Maintenant, vous pouvez essayer d'exécuter tous les tests et ils devraient passer.

NOTE : Si vous avez suivi les étapes de la section Authentification de base, vous devrez ajouter l'autorisation à chaque en-tête de requête pour que tous les tests passent :

post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }, headers: { Authorization: ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") }

8.2 Types de requêtes disponibles pour les tests fonctionnels

Si vous êtes familier avec le protocole HTTP, vous saurez que get est un type de requête. Il existe 6 types de requêtes pris en charge dans les tests fonctionnels de Rails :

  • get
  • post
  • patch
  • put
  • head
  • delete
Tous les types de requêtes ont des méthodes équivalentes que vous pouvez utiliser. Dans une application C.R.U.D. typique, vous utiliserez plus souvent get, post, put et delete.

Les tests fonctionnels ne vérifient pas si le type de requête spécifié est accepté par l'action, nous nous préoccupons davantage du résultat. Les tests de requête existent pour ce cas d'utilisation afin de rendre vos tests plus significatifs.

8.3 Test des requêtes XHR (Ajax)

Pour tester les requêtes Ajax, vous pouvez spécifier l'option xhr: true pour les méthodes get, post, patch, put et delete. Par exemple :

test "requête ajax" do
  article = articles(:one)
  get article_url(article), xhr: true

  assert_equal "hello world", @response.body
  assert_equal "text/javascript", @response.media_type
end

8.4 Les trois Hash de l'Apocalypse

Après qu'une requête a été effectuée et traitée, vous aurez 3 objets Hash prêts à être utilisés :

  • cookies - Tous les cookies qui sont définis
  • flash - Tous les objets présents dans le flash
  • session - Tout objet présent dans les variables de session

Comme c'est le cas avec les objets Hash normaux, vous pouvez accéder aux valeurs en référençant les clés par chaîne de caractères. Vous pouvez également les référencer par leur nom de symbole. Par exemple :

flash["gordon"]               flash[:gordon]
session["shmession"]          session[:shmession]
cookies["are_good_for_u"]     cookies[:are_good_for_u]

8.5 Variables d'instance disponibles

Après qu'une requête a été effectuée, vous avez également accès à trois variables d'instance dans vos tests fonctionnels :

  • @controller - Le contrôleur qui traite la requête
  • @request - L'objet de requête
  • @response - L'objet de réponse
class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "devrait obtenir l'index" do
    get articles_url

    assert_equal "index", @controller.action_name
    assert_equal "application/x-www-form-urlencoded", @request.media_type
    assert_match "Articles", @response.body
  end
end

8.6 Définition des en-têtes et des variables CGI

Les en-têtes HTTP et les variables CGI peuvent être transmis en tant qu'en-têtes :

# définition d'un en-tête HTTP
get articles_url, headers: { "Content-Type": "text/plain" } # simuler la requête avec un en-tête personnalisé

# définition d'une variable CGI
get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simuler la requête avec une variable d'environnement personnalisée

8.7 Test des notifications flash

Si vous vous souvenez de notre discussion précédente, l'un des trois Hash de l'Apocalypse était flash.

Nous voulons ajouter un message flash à notre application de blog chaque fois que quelqu'un crée avec succès un nouvel article.

Commençons par ajouter cette assertion à notre test test_should_create_article :

test "devrait créer un article" do
  assert_difference("Article.count") do
    post articles_url, params: { article: { title: "Some title" } }
  end

  assert_redirected_to article_path(Article.last)
  assert_equal "Article a été créé avec succès.", flash[:notice]
end

Si nous exécutons maintenant notre test, nous devrions voir un échec :

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Options d'exécution : -n test_should_create_article --seed 32266

# Exécution :

F

Terminé en 0.114870s, 8.7055 exécutions/s, 34.8220 assertions/s.

  1) Échec :
ArticlesControllerTest#test_should_create_article [/test/controllers/articles_controller_test.rb:16]:
--- expected
+++ actual
@@ -1 +1 @@
-"Article a été créé avec succès."
+nil

1 exécution, 4 assertions, 1 échec, 0 erreurs, 0 ignorés

Implémentons maintenant le message flash dans notre contrôleur. Notre action :create devrait maintenant ressembler à ceci :

def create
  @article = Article.new(article_params)

  if @article.save
    flash[:notice] = "Article a été créé avec succès."
    redirect_to @article
  else
    render "new"
  end
end

Maintenant, si nous exécutons nos tests, nous devrions voir qu'ils réussissent :

$ bin/rails test test/controllers/articles_controller_test.rb -n test_should_create_article
Options d'exécution : -n test_should_create_article --seed 18981

# Exécution :

.

Terminé en 0.081972s, 12.1993 exécutions/s, 48.7972 assertions/s.

1 exécution, 4 assertions, 0 échecs, 0 erreurs, 0 ignorés

8.8 Mise en pratique

À ce stade, notre contrôleur Articles teste les actions :index, :new et :create. Que faire avec les données existantes ?

Écrivons un test pour l'action :show :

test "devrait afficher l'article" do
  article = articles(:one)
  get article_url(article)
  assert_response :success
end

Souvenez-vous de notre discussion précédente sur les fixtures, la méthode articles() nous donnera accès à nos fixtures d'articles.

Et comment supprimer un article existant ?

test "devrait supprimer l'article" do
  article = articles(:one)
  assert_difference("Article.count", -1) do
    delete article_url(article)
  end

  assert_redirected_to articles_path
end

Nous pouvons également ajouter un test pour mettre à jour un article existant.

test "devrait mettre à jour l'article" do
  article = articles(:one)

  patch article_url(article), params: { article: { title: "updated" } }

  assert_redirected_to article_path(article)
  # Rechargez l'association pour récupérer les données mises à jour et vérifiez que le titre est mis à jour.
  article.reload
  assert_equal "updated", article.title
end

Remarquez que nous commençons à voir une certaine duplication dans ces trois tests, ils accèdent tous les deux aux mêmes données de fixture d'article. Nous pouvons rendre cela plus D.R.Y. en utilisant les méthodes setup et teardown fournies par ActiveSupport::Callbacks.

Maintenant, notre test devrait ressembler à quelque chose comme ce qui suit. Ignorez les autres tests pour l'instant, nous les laissons de côté pour des raisons de concision. ```ruby require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest # appelé avant chaque test individuel setup do @article = articles(:one) end

# appelé après chaque test individuel teardown do # lorsqu'un contrôleur utilise le cache, il est conseillé de le réinitialiser ensuite Rails.cache.clear end

test "should show article" do # Réutilise la variable d'instance @article définie dans le setup get article_url(@article) assert_response :success end

test "should destroy article" do assert_difference("Article.count", -1) do delete article_url(@article) end

assert_redirected_to articles_path

end

test "should update article" do patch article_url(@article), params: { article: { title: "updated" } }

assert_redirected_to article_path(@article)
# Recharge l'association pour récupérer les données mises à jour et vérifie que le titre est mis à jour.
@article.reload
assert_equal "updated", @article.title

end end ```

Comme pour les autres rappels dans Rails, les méthodes setup et teardown peuvent également être utilisées en passant un bloc, une lambda ou un nom de méthode sous forme de symbole à appeler.

8.9 Aides de test

Pour éviter la duplication de code, vous pouvez ajouter vos propres aides de test. L'aide de connexion peut être un bon exemple :

# test/test_helper.rb

module SignInHelper
  def sign_in_as(user)
    post sign_in_url(email: user.email, password: user.password)
  end
end

class ActionDispatch::IntegrationTest
  include SignInHelper
end
require "test_helper"

class ProfileControllerTest < ActionDispatch::IntegrationTest
  test "should show profile" do
    # l'aide est maintenant réutilisable à partir de n'importe quel cas de test de contrôleur
    sign_in_as users(:david)

    get profile_url
    assert_response :success
  end
end

8.9.1 Utilisation de fichiers séparés

Si vous trouvez que vos aides encombrent test_helper.rb, vous pouvez les extraire dans des fichiers séparés. Un bon endroit pour les stocker est test/lib ou test/test_helpers.

# test/test_helpers/multiple_assertions.rb
module MultipleAssertions
  def assert_multiple_of_forty_two(number)
    assert (number % 42 == 0), "expected #{number} to be a multiple of 42"
  end
end

Ces aides peuvent ensuite être explicitement requises au besoin et incluses au besoin

require "test_helper"
require "test_helpers/multiple_assertions"

class NumberTest < ActiveSupport::TestCase
  include MultipleAssertions

  test "420 is a multiple of forty two" do
    assert_multiple_of_forty_two 420
  end
end

ou elles peuvent continuer à être incluses directement dans les classes parent pertinentes

# test/test_helper.rb
require "test_helpers/sign_in_helper"

class ActionDispatch::IntegrationTest
  include SignInHelper
end

8.9.2 Requête préalable des aides

Il peut être pratique de requérir préalablement les aides dans test_helper.rb afin que vos fichiers de test y aient un accès implicite. Cela peut être accompli en utilisant le globbing, comme suit

# test/test_helper.rb
Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }

Cela a pour inconvénient d'augmenter le temps de démarrage, par rapport à la requête manuelle uniquement des fichiers nécessaires dans vos tests individuels.

9 Test des routes

Comme tout le reste de votre application Rails, vous pouvez tester vos routes. Les tests de routes se trouvent dans test/controllers/ ou font partie des tests de contrôleur.

REMARQUE : Si votre application a des routes complexes, Rails fournit plusieurs aides utiles pour les tester.

Pour plus d'informations sur les assertions de routage disponibles dans Rails, consultez la documentation de l'API pour ActionDispatch::Assertions::RoutingAssertions.

10 Test des vues

Tester la réponse à votre requête en vérifiant la présence d'éléments HTML clés et leur contenu est une façon courante de tester les vues de votre application. Comme les tests de routes, les tests de vues se trouvent dans test/controllers/ ou font partie des tests de contrôleur. La méthode assert_select vous permet d'interroger les éléments HTML de la réponse en utilisant une syntaxe simple mais puissante.

Il existe deux formes de assert_select :

assert_select(selector, [equality], [message]) garantit que la condition d'égalité est respectée sur les éléments sélectionnés par le sélecteur. Le sélecteur peut être une expression de sélecteur CSS (String) ou une expression avec des valeurs de substitution.

assert_select(element, selector, [equality], [message]) garantit que la condition d'égalité est respectée sur tous les éléments sélectionnés par le sélecteur à partir de l'élément (instance de Nokogiri::XML::Node ou Nokogiri::XML::NodeSet) et de ses descendants.

Par exemple, vous pouvez vérifier le contenu de l'élément de titre dans votre réponse avec :

assert_select "title", "Welcome to Rails Testing Guide"

Vous pouvez également utiliser des blocs assert_select imbriqués pour des investigations plus approfondies.

Dans l'exemple suivant, le assert_select interne pour li.menu_item s'exécute dans la collection d'éléments sélectionnés par le bloc externe :

assert_select "ul.navigation" do
  assert_select "li.menu_item"
end

Une collection d'éléments sélectionnés peut être itérée afin que assert_select puisse être appelé séparément pour chaque élément.

Par exemple, si la réponse contient deux listes ordonnées, chacune avec quatre éléments de liste imbriqués, les tests suivants réussiront tous les deux.

assert_select "ol" do |elements|
  elements.each do |element|
    assert_select element, "li", 4
  end
end

assert_select "ol" do
  assert_select "li", 8
end

Cette assertion est assez puissante. Pour une utilisation plus avancée, consultez sa documentation.

10.1 Assertions supplémentaires basées sur la vue

Il existe d'autres assertions principalement utilisées dans les tests de vues :

Assertion Objectif
assert_select_email Vous permet de faire des assertions sur le corps d'un e-mail.
assert_select_encoded Vous permet de faire des assertions sur du HTML encodé. Il le fait en décodant le contenu de chaque élément, puis en appelant le bloc avec tous les éléments non encodés.
css_select(selector) ou css_select(element, selector) Retourne un tableau de tous les éléments sélectionnés par le sélecteur. Dans la deuxième variante, il correspond d'abord à l'élément de base, puis essaie de faire correspondre l'expression du sélecteur à l'un de ses enfants. Si aucune correspondance n'est trouvée, les deux variantes renvoient un tableau vide.

Voici un exemple d'utilisation de assert_select_email :

assert_select_email do
  assert_select "small", "Please click the 'Unsubscribe' link if you want to opt-out."
end

11 Testing Helpers

Un helper est simplement un module simple dans lequel vous pouvez définir des méthodes disponibles dans vos vues.

Pour tester les helpers, il vous suffit de vérifier que la sortie de la méthode du helper correspond à ce que vous attendez. Les tests liés aux helpers se trouvent dans le répertoire test/helpers.

Supposons que nous ayons le helper suivant :

module UsersHelper
  def link_to_user(user)
    link_to "#{user.first_name} #{user.last_name}", user
  end
end

Nous pouvons tester la sortie de cette méthode de la manière suivante :

class UsersHelperTest < ActionView::TestCase
  test "should return the user's full name" do
    user = users(:david)

    assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user)
  end
end

De plus, puisque la classe de test étend ActionView::TestCase, vous avez accès aux méthodes d'aide de Rails telles que link_to ou pluralize.

12 Testing Your Mailers

Tester les classes de mailer nécessite des outils spécifiques pour faire un travail approfondi.

12.1 Garder le facteur sous contrôle

Vos classes de mailer - comme toutes les autres parties de votre application Rails - doivent être testées pour s'assurer qu'elles fonctionnent comme prévu.

Les objectifs des tests de vos classes de mailer sont de s'assurer que :

  • les e-mails sont traités (créés et envoyés)
  • le contenu de l'e-mail est correct (sujet, expéditeur, corps, etc.)
  • les bons e-mails sont envoyés au bon moment

12.1.1 De tous les côtés

Il y a deux aspects pour tester votre mailer, les tests unitaires et les tests fonctionnels. Dans les tests unitaires, vous exécutez le mailer de manière isolée avec des entrées étroitement contrôlées et comparez la sortie à une valeur connue (un fixture). Dans les tests fonctionnels, vous ne testez pas tant les détails minutieux produits par le mailer ; au lieu de cela, nous testons si nos contrôleurs et modèles utilisent le mailer de la bonne manière. Vous testez pour prouver que le bon e-mail a été envoyé au bon moment.

12.2 Tests unitaires

Pour tester si votre mailer fonctionne comme prévu, vous pouvez utiliser des tests unitaires pour comparer les résultats réels du mailer avec des exemples pré-écrits de ce qui devrait être produit.

12.2.1 La revanche des fixtures

Dans le cadre des tests unitaires d'un mailer, les fixtures sont utilisées pour fournir un exemple de ce à quoi la sortie devrait ressembler. Étant donné qu'il s'agit d'e-mails d'exemple et non de données Active Record comme les autres fixtures, ils sont conservés dans leur propre sous-répertoire séparé des autres fixtures. Le nom du répertoire dans test/fixtures correspond directement au nom du mailer. Ainsi, pour un mailer nommé UserMailer, les fixtures doivent résider dans le répertoire test/fixtures/user_mailer.

Si vous avez généré votre mailer, le générateur ne crée pas de fixtures fictives pour les actions du mailer. Vous devrez créer ces fichiers vous-même comme décrit ci-dessus.

12.2.2 Le cas de test de base

Voici un test unitaire pour tester un mailer nommé UserMailer dont l'action invite est utilisée pour envoyer une invitation à un ami. Il s'agit d'une version adaptée du test de base créé par le générateur pour une action invite.

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Créez l'e-mail et stockez-le pour d'autres assertions
    email = UserMailer.create_invite("[email protected]",
                                     "[email protected]", Time.now)

    # Envoyez l'e-mail, puis testez s'il a été mis en file d'attente
    assert_emails 1 do
      email.deliver_now
    end

    # Testez que le corps de l'e-mail envoyé contient ce à quoi nous nous attendons
    assert_equal ["[email protected]"], email.from
    assert_equal ["[email protected]"], email.to
    assert_equal "You have been invited by [email protected]", email.subject
    assert_equal read_fixture("invite").join, email.body.to_s
  end
end

Dans le test, nous créons l'e-mail et stockons l'objet renvoyé dans la variable email. Ensuite, nous nous assurons qu'il a été envoyé (le premier assert), puis, dans le deuxième groupe d'assertions, nous nous assurons que l'e-mail contient bien ce que nous attendons. L'aide read_fixture est utilisée pour lire le contenu de ce fichier.

NOTE : email.body.to_s est présent lorsqu'il n'y a qu'une seule partie (HTML ou texte). Si le mailer en fournit les deux, vous pouvez tester votre fixture par rapport à des parties spécifiques avec email.text_part.body.to_s ou email.html_part.body.to_s.

Voici le contenu de la fixture invite :

Salut [email protected],

Tu as été invité.

Amicalement !

C'est le bon moment pour en savoir un peu plus sur l'écriture de tests pour vos mailers. La ligne ActionMailer::Base.delivery_method = :test dans config/environments/test.rb définit le mode de livraison en mode test afin que l'e-mail ne soit pas réellement envoyé (utile pour éviter de spammer vos utilisateurs lors des tests), mais qu'il soit plutôt ajouté à un tableau (ActionMailer::Base.deliveries).

NOTE : Le tableau ActionMailer::Base.deliveries n'est réinitialisé automatiquement que dans les tests ActionMailer::TestCase et ActionDispatch::IntegrationTest. Si vous souhaitez repartir à zéro en dehors de ces cas de test, vous pouvez le réinitialiser manuellement avec : ActionMailer::Base.deliveries.clear

12.2.3 Test des e-mails en file d'attente

Vous pouvez utiliser l'assertion assert_enqueued_email_with pour confirmer que l'e-mail a été mis en file d'attente avec tous les arguments de la méthode mailer attendus et/ou les paramètres du mailer paramétrés. Cela vous permet de faire correspondre tous les e-mails qui ont été mis en file d'attente avec la méthode deliver_later.

Comme dans le cas de test de base, nous créons l'e-mail et stockons l'objet renvoyé dans la variable email. Les exemples suivants incluent des variations de passage d'arguments et/ou de paramètres.

Cet exemple vérifiera que l'e-mail a été mis en file d'attente avec les bons arguments :

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Crée l'e-mail et le stocke pour d'autres assertions
    email = UserMailer.create_invite("[email protected]", "[email protected]")

    # Vérifie que l'e-mail a été mis en file d'attente avec les bons arguments
    assert_enqueued_email_with UserMailer, :create_invite, args: ["[email protected]", "[email protected]"] do
      email.deliver_later
    end
  end
end

Cet exemple vérifiera qu'un mailer a été mis en file d'attente avec les bons arguments nommés de la méthode mailer en passant un hash des arguments en tant que args :

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Crée l'e-mail et le stocke pour d'autres assertions
    email = UserMailer.create_invite(from: "[email protected]", to: "[email protected]")

    # Vérifie que l'e-mail a été mis en file d'attente avec les bons arguments nommés
    assert_enqueued_email_with UserMailer, :create_invite, args: [{ from: "[email protected]",
                                                                    to: "[email protected]" }] do
      email.deliver_later
    end
  end
end

Cet exemple vérifiera qu'un mailer paramétré a été mis en file d'attente avec les bons paramètres et arguments du mailer. Les paramètres du mailer sont passés en tant que params et les arguments de la méthode mailer en tant que args :

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Crée l'e-mail et le stocke pour d'autres assertions
    email = UserMailer.with(all: "good").create_invite("[email protected]", "[email protected]")

    # Vérifie que l'e-mail a été mis en file d'attente avec les bons paramètres et arguments du mailer
    assert_enqueued_email_with UserMailer, :create_invite, params: { all: "good" },
                                                           args: ["[email protected]", "[email protected]"] do
      email.deliver_later
    end
  end
end

Cet exemple montre une autre façon de vérifier qu'un mailer paramétré a été mis en file d'attente avec les bons paramètres :

require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Crée l'e-mail et le stocke pour d'autres assertions
    email = UserMailer.with(to: "[email protected]").create_invite

    # Vérifie que l'e-mail a été mis en file d'attente avec les bons paramètres du mailer
    assert_enqueued_email_with UserMailer.with(to: "[email protected]"), :create_invite do
      email.deliver_later
    end
  end
end

12.3 Tests fonctionnels et système

Les tests unitaires nous permettent de tester les attributs de l'e-mail, tandis que les tests fonctionnels et système nous permettent de tester si les interactions de l'utilisateur déclenchent correctement l'envoi de l'e-mail. Par exemple, vous pouvez vérifier que l'opération d'invitation d'un ami envoie un e-mail de manière appropriée :

# Test d'intégration
require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "invite friend" do
    # Vérifie la différence dans ActionMailer::Base.deliveries
    assert_emails 1 do
      post invite_friend_url, params: { email: "[email protected]" }
    end
  end
end
# Test système
require "test_helper"

class UsersTest < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome

  test "inviting a friend" do
    visit invite_users_url
    fill_in "Email", with: "[email protected]"
    assert_emails 1 do
      click_on "Invite"
    end
  end
end

NOTE : La méthode assert_emails n'est pas liée à une méthode de livraison particulière et fonctionnera avec les e-mails envoyés avec la méthode deliver_now ou deliver_later. Si nous voulons explicitement vérifier que l'e-mail a été mis en file d'attente, nous pouvons utiliser les méthodes assert_enqueued_email_with (exemples ci-dessus) ou assert_enqueued_emails. Plus d'informations peuvent être trouvées dans la documentation ici.

13 Tests d'emploi

Étant donné que vos emplois personnalisés peuvent être mis en file d'attente à différents niveaux à l'intérieur de votre application, vous devrez tester à la fois les emplois eux-mêmes (leur comportement lorsqu'ils sont mis en file d'attente) et que d'autres entités les mettent correctement en file d'attente.

13.1 Un cas de test de base

Par défaut, lorsque vous générez un emploi, un test associé sera également généré sous le répertoire test/jobs. Voici un exemple de test avec un emploi de facturation :

require "test_helper"

class BillingJobTest < ActiveJob::TestCase
  test "que le compte est facturé" do
    BillingJob.perform_now(account, product)
    assert account.reload.charged_for?(product)
  end
end

Ce test est assez simple et ne fait que vérifier si l'emploi a effectué le travail attendu.

13.2 Assertions personnalisées et tests d'emplois à l'intérieur d'autres composants

Active Job est livré avec un ensemble d'assertions personnalisées qui peuvent être utilisées pour réduire la verbosité des tests. Pour une liste complète des assertions disponibles, consultez la documentation de l'API pour ActiveJob::TestHelper.

Il est bon de vérifier que vos emplois sont correctement mis en file d'attente ou exécutés partout où vous les invoquez (par exemple, à l'intérieur de vos contrôleurs). C'est précisément là que les assertions personnalisées fournies par Active Job sont très utiles. Par exemple, dans un modèle, vous pouvez confirmer qu'un emploi a été mis en file d'attente :

require "test_helper"

class ProductTest < ActiveSupport::TestCase
  include ActiveJob::TestHelper

  test "planification de l'emploi de facturation" do
    assert_enqueued_with(job: BillingJob) do
      product.charge(account)
    end
    assert_not account.reload.charged_for?(product)
  end
end

L'adaptateur par défaut, :test, n'exécute pas les emplois lorsqu'ils sont mis en file d'attente. Vous devez lui indiquer quand vous voulez que les emplois soient exécutés :

require "test_helper"

class ProductTest < ActiveSupport::TestCase
  include ActiveJob::TestHelper

  test "planification de l'emploi de facturation" do
    perform_enqueued_jobs(only: BillingJob) do
      product.charge(account)
    end
    assert account.reload.charged_for?(product)
  end
end

Tous les emplois précédemment exécutés et mis en file d'attente sont effacés avant chaque exécution de test, vous pouvez donc supposer en toute sécurité qu'aucun emploi n'a déjà été exécuté dans le cadre de chaque test.

14 Test de Action Cable

Étant donné que Action Cable est utilisé à différents niveaux à l'intérieur de votre application, vous devrez tester à la fois les canaux, les classes de connexion elles-mêmes, et que d'autres entités diffusent les messages corrects.

14.1 Cas de test de connexion

Par défaut, lorsque vous générez une nouvelle application Rails avec Action Cable, un test pour la classe de connexion de base (ApplicationCable::Connection) est également généré sous le répertoire test/channels/application_cable.

Les tests de connexion visent à vérifier si les identifiants d'une connexion sont correctement attribués ou si des demandes de connexion incorrectes sont rejetées. Voici un exemple :

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "se connecte avec des paramètres" do
    # Simule l'ouverture d'une connexion en appelant la méthode `connect`
    connect params: { user_id: 42 }

    # Vous pouvez accéder à l'objet Connection via `connection` dans les tests
    assert_equal connection.user_id, "42"
  end

  test "rejette la connexion sans paramètres" do
    # Utilisez l'assertion `assert_reject_connection` pour vérifier que
    # la connexion est rejetée
    assert_reject_connection { connect }
  end
end

Vous pouvez également spécifier les cookies de requête de la même manière que vous le faites dans les tests d'intégration :

test "se connecte avec des cookies" do
  cookies.signed[:user_id] = "42"

  connect

  assert_equal connection.user_id, "42"
end

Consultez la documentation de l'API pour ActionCable::Connection::TestCase pour plus d'informations.

14.2 Cas de test de canal

Par défaut, lorsque vous générez un canal, un test associé sera également généré sous le répertoire test/channels. Voici un exemple de test avec un canal de discussion :

require "test_helper"

class ChatChannelTest < ActionCable::Channel::TestCase
  test "s'abonne et diffuse pour la salle" do
    # Simule la création d'un abonnement en appelant `subscribe`
    subscribe room: "15"

    # Vous pouvez accéder à l'objet Channel via `subscription` dans les tests
    assert subscription.confirmed?
    assert_has_stream "chat_15"
  end
end

Ce test est assez simple et ne fait que vérifier si le canal abonne la connexion à un flux particulier.

Vous pouvez également spécifier les identifiants de connexion sous-jacents. Voici un exemple de test avec un canal de notifications web :

require "test_helper"

class WebNotificationsChannelTest < ActionCable::Channel::TestCase
  test "s'abonne et diffuse pour l'utilisateur" do
    stub_connection current_user: users(:john)

    subscribe

    assert_has_stream_for users(:john)
  end
end

Consultez la documentation de l'API pour ActionCable::Channel::TestCase pour plus d'informations.

14.3 Assertions personnalisées et tests de diffusion à l'intérieur d'autres composants

Action Cable est livré avec un ensemble d'assertions personnalisées qui peuvent être utilisées pour réduire la verbosité des tests. Pour une liste complète des assertions disponibles, consultez la documentation de l'API pour ActionCable::TestHelper.

Il est bon de vérifier que le bon message a été diffusé à l'intérieur d'autres composants (par exemple, à l'intérieur de vos contrôleurs). C'est précisément là que les assertions personnalisées fournies par Action Cable sont très utiles. Par exemple, dans un modèle : ```ruby require "test_helper"

class ProductTest < ActionCable::TestCase test "diffuser le statut après la charge" do assert_broadcast_on("products:#{product.id}", type: "charged") do product.charge(account) end end end ```

Si vous souhaitez tester la diffusion effectuée avec Channel.broadcast_to, vous devez utiliser Channel.broadcasting_for pour générer un nom de flux sous-jacent :

# app/jobs/chat_relay_job.rb
class ChatRelayJob < ApplicationJob
  def perform(room, message)
    ChatChannel.broadcast_to room, text: message
  end
end
# test/jobs/chat_relay_job_test.rb
require "test_helper"

class ChatRelayJobTest < ActiveJob::TestCase
  include ActionCable::TestHelper

  test "diffuser un message dans la salle" do
    room = rooms(:all)

    assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Salut !") do
      ChatRelayJob.perform_now(room, "Salut !")
    end
  end
end

15 Test de chargement anticipé

Normalement, les applications ne se chargent pas de manière anticipée dans les environnements development ou test pour accélérer les choses. Mais elles le font dans l'environnement production.

Si un fichier du projet ne peut pas être chargé pour une raison quelconque, il vaut mieux le détecter avant de le déployer en production, n'est-ce pas ?

15.1 Intégration continue

Si votre projet dispose d'une intégration continue, le chargement anticipé en CI est un moyen facile de s'assurer que l'application se charge de manière anticipée.

Les CI définissent généralement une variable d'environnement pour indiquer que la suite de tests s'exécute là-bas. Par exemple, cela pourrait être CI :

# config/environments/test.rb
config.eager_load = ENV["CI"].present?

À partir de Rails 7, les applications nouvellement générées sont configurées de cette manière par défaut.

15.2 Suites de tests minimales

Si votre projet n'a pas d'intégration continue, vous pouvez quand même charger de manière anticipée dans la suite de tests en appelant Rails.application.eager_load! :

15.2.1 Minitest

require "test_helper"

class ZeitwerkComplianceTest < ActiveSupport::TestCase
  test "charge de manière anticipée tous les fichiers sans erreur" do
    assert_nothing_raised { Rails.application.eager_load! }
  end
end

15.2.2 RSpec

require "rails_helper"

RSpec.describe "Conformité Zeitwerk" do
  it "charge de manière anticipée tous les fichiers sans erreur" do
    expect { Rails.application.eager_load! }.not_to raise_error
  end
end

16 Ressources de test supplémentaires

16.1 Test du code dépendant du temps

Rails fournit des méthodes d'aide intégrées qui vous permettent de vérifier que votre code sensible au temps fonctionne comme prévu.

L'exemple suivant utilise l'aide travel_to :

# Étant donné qu'un utilisateur est éligible pour offrir un mois après son inscription.
user = User.create(name: "Gaurish", activation_date: Date.new(2004, 10, 24))
assert_not user.applicable_for_gifting?

travel_to Date.new(2004, 11, 24) do
  # À l'intérieur du bloc `travel_to`, `Date.current` est simulé
  assert_equal Date.new(2004, 10, 24), user.activation_date
  assert user.applicable_for_gifting?
end

# Le changement n'était visible que dans le bloc `travel_to`.
assert_equal Date.new(2004, 10, 24), user.activation_date

Veuillez consulter la référence de l'API ActiveSupport::Testing::TimeHelpers pour plus d'informations sur les aides temporelles disponibles.

Retour d'information

Vous êtes encouragé à contribuer à l'amélioration de la qualité de ce guide.

Veuillez contribuer si vous trouvez des fautes de frappe ou des erreurs factuelles. Pour commencer, vous pouvez lire notre contribution à la documentation section.

Vous pouvez également trouver du contenu incomplet ou des informations qui ne sont pas à jour. Veuillez ajouter toute documentation manquante pour la version principale. Assurez-vous de vérifier Edge Guides d'abord pour vérifier si les problèmes ont déjà été résolus ou non sur la branche principale. Consultez les Directives des guides Ruby on Rails pour le style et les conventions.

Si pour une raison quelconque vous repérez quelque chose à corriger mais ne pouvez pas le faire vous-même, veuillez ouvrir un problème.

Et enfin, toute discussion concernant la documentation de Ruby on Rails est la bienvenue sur le Forum officiel de Ruby on Rails.