Jasmine partie 3 : Gestion D'événements Asynchrones

Publié par Jean-Nicolas Viens le jeudi 25 juillet 2013 à 07:46

Ceci est le dernier, mais non le moindre, d’une série de trois articles à propos de Jasmine. Je vous invite à regarder d’abord le premier et le deuxième article avant celui-ci. Nous continuerons avec le concept de mocks vu lors de la deuxième partie afin de pouvoir contrôler le temps! Cela nous permettera, entre autres, de tester des comportements répétés (à toutes les X secondes), ou encore d’accélerer des effets graphiques pour améliorer les performances de nos tests.

Tester des comportements asynchrones

Il existe plusieurs cas de figures où l’on veut attendre la fin d’une action avant d’en valider le résultat. Par exemple, on peut vouloir attendre la fin d’un appel AJAX avant de s’assurer que le contenu d’un conteneur a été changé, ou qu’une fonction a été appelée. Cependant, nous allons profiter de ces exemples pour introduire une extension à Jasmine : jasmine-jquery. Celle-ci nous permet d’utiliser la syntaxe expect($object_jquery), en plus de plusieurs nouvelles méthodes pour valider le contenu de cet objet jQuery. Par exemple, supposons que nous avons un <div> avec fenetre comme id :

expect($("#fenetre")).toBeVisible();

Vous trouverez toutes les informations pertinentes sur ces nouvelles méthodes sur la page github de cette extension. Nous n’élaborerons pas sur ce sujet ici, mais sachez que cette extension permet également l’utilisation de fixtures HTML, de même que l’utilisation de mocks pour les événements.

Illustrons la valeur de jasmine-query et des mocks dans un cas simple mais fort typique en développement web. On veut s’assurer que lorsque le bouton “fermer” de notre fenêtre est appelé, alors la fenêtre se ferme et une autre s’ouvre. Comment vérifier cela? Simplement en vérifiant si la fenêtre #2 est maintenant visible après la fermeture de la fenêtre #1. Si la fenêtre se ferme instantanément, ce n’est peut-être pas un problème, mais qu’arrive-t-il si la fenêtre se ferme avec une jolie animation? Et bien, tout au long de l’animation elle sera visible et notre test échouera!

Jasmine nous permet de séquencer des blocs de code avec la méthode runs(). Ceux-ci seront simplement exécutés dans leur entièreté les uns après les autres dans l’ordre. On peut également ajouter un bloc waitsFor() qui sera exécuté tant et aussi longtemps que celui-ci ne retournera pas vrai ou qu’un timeout ne sera pas atteint. Un bloc runs() ne sera jamais executé tant et aussi longtemps que le bloc waitsFor() qui le précède ne retournera pas true. Tout cela est certainement plus clair avec un exemple :

Notez la structure de ce dernier exemple:

  1. Un bloc de code runs() qui se termine par un appel asynchrone (en jQuery, la fonction animate retourne avant d’avoir complété l’animation en question)
  2. Un bloc de code waitsFor() qui exprime une condition à attendre (note: le timeout est optionnel)
  3. Un bloc de code runs() avec une assertion

Le seul problème de cette technique est que le test est très verbeux, il y a beaucoup de bruit pour rien. Le problème peut être mitigé par l’utilisation d’un langage tel le coffeescript. Voici le même exemple, écrit en coffeescript :

Cependant, il est à noter que ce genre de test n’est pas souvent nécessaire. Dans ce cas particulier, il aurait été préférable de désactiver toutes les animations jQuery tout simplement. On peut faire cela avec cette ligne de code :

jQuery.fx.off = true;

Ce qui a pour effet que toutes les animations sont instantanées.

Bien qu’il est pratique de pouvoir faire tout cela, il est généralement souhaitable de ne pas (ou peu) se coupler du DOM lors de tests Javascript, mais cela sort de la portée de cet article.

Contrôler le temps

Jasmine nous permet également de contrôler le temps en utilisant des mocks sur les fonctions setTimeout et setInterval du Javascript. Heureusement, cette complexité est encapsulée pour nous. Nous avons simplement à dire à Jasmine d’utiliser un mock pour le temps, ce qui fait qu’aucune fonction passée à setTimeout et setInterval ne sera executée tant qu’on ne l’aura pas demandé. Le temps sera “gelé” et nous pourrons alors avancer le temps X secondes à la fois, selon les besoins du test. Pour dire à Jasmine d’utiliser un mock pour gérer le temps, on utilise :

jasmine.Clock.useMock();

Cela installe le mock et s’assure de le désinstaller après chaque test. On peut donc l’utiliser sans danger de collision avec un autre test. On peut également l’utiliser dans le beforeEach(). Par la suite, on utilise la fonction tick(<millisecondes>) pour faire avancer le temps. Voici un exemple typique d’un test :

jasmine.Clock.useMock();
// -> action qui utilise le setTimeout ou setInterval
jasmine.Clock.tick("nombre en millisecondes")
// -> validation que l'action a été exécutée

Il est également possible d’utiliser d’autres fonctions de Clock, dont voici les plus utilisées :

  • reset : retourne le compteur à 0
  • installMock : comme useMock(), mais ne désinstalle pas automatiquement le mock après le test. Il est généralement conseillé d’utiliser useMock().
  • uninstallMock : retire le mock manuellement. Attention, si vous avez utilisé useMock(), vous risquez d’avoir une exception vu que uninstallMock() sera appelé automatiquement une 2e fois.
  • isInstalled : retourne vrai ou faux, selon que le mock pour le temps est installé
  • assertInstalled : même chose, mais lance une exception de type Error

Voici un exemple complet, avec une fonction qui valide l’état d’une session d’un usager à chaque seconde.

Il est possible de faire exécuter instantanément des animations jQuery avec cette technique, mais cela requiert de placer ce bout de code avant de charger jQuery :

window.webkitRequestAnimationFrame = null;
window.mozRequestAnimationFrame = null;
window.oRequestAnimationFrame = null;
window.requestAnimationFrame = null;

Ce code a pour but de désactiver la gestion de “frames” dans le navigateur puisque jQuery utilise cette fonction plutôt que setTimeout désormais. On peut par la suite exécuter pas-à-pas une animation.

Il est pertinent de noter aussi que l’extension jasmine-ajax utilise le même principe pour détourner les appels AJAX, soit :

jasmine.Ajax.useMock();

Je n’entrerai pas dans les détails de cette extension, mais elle vaut certainement le coup d’oeil!

Conclusion

Bien que nous ayons vu une grande partie des fonctionnalités qu’offre Jasmine, plusieurs questions restent. Il y a beaucoup de facteurs à prendre en compte lors de la conception de tests Javascript, principalement : quoi tester? Il faut éviter de trop se coupler sur le DOM et se concentrer sur la logique d’affaires qui se retrouve du côté du client. Il y a aussi plusieurs pièges à éviter, et tout cela fera vraisemblablement partie d’un prochain article sur les tests Javascript. Il faut mentionner également que mon collègue Vincent a produit un article de même nature que celui-ci sur qUnit, outil de tests offert par les développeurs de jQuery.

Cette série d’articles à propos de Jasmine est terminée en soi, mais il reste un sujet à traiter : l’intégration avec nos outils. Dans un prochain article, vous pourrez voir comment intégrer Jasmine.js à Visual Studio, à votre build dans TFS et à une application Rails.

blog comments powered by Disqus

0 Comments:

Post a comment

Comments have been closed for this post.