AngularJS est conçu avec la testabilité à l'esprit. L'injection de dépendance est l'une des caractéristiques importantes du cadre qui facilite les tests unitaires. AngularJS définit un moyen de modulariser soigneusement l'application et de le diviser en différents composants tels que des contrôleurs, des directives, des filtres ou des animations. Ce modèle de développement signifie que les pièces individuelles fonctionnent isolément et que l'application peut évoluer facilement sur une longue période. Au fur et à mesure que l'extensibilité et la testabilité vont de pair, il est facile de tester le code angulaire.
Selon la définition des tests unitaires, le système testé doit être testé isolément. Ainsi, tous les objets externes nécessaires au système doivent être remplacés par des objets simulés. Comme le nom lui-même le dit, les objets simulés n'effectue pas une tâche réelle; Ils sont plutôt utilisés pour répondre aux attentes du système testé. Si vous avez besoin d'un rafraîchissement sur la moquerie, veuillez vous référer à l'un de mes articles précédents: les dépendances moqueuses dans les tests angularjs.
Dans cet article, je partagerai un ensemble de conseils sur les services de test, les contrôleurs et les fournisseurs dans AngularJS. Les extraits de code ont été écrits à l'aide de Jasmine et peuvent être exécutés avec le Karma Test Runner. Vous pouvez télécharger le code utilisé dans cet article à partir de notre dépôt GitHub, où vous trouverez également des instructions sur l'exécution des tests.
sont l'un des composants les plus courants d'une application AngularJS. Ils fournissent un moyen de définir la logique réutilisable dans un endroit central afin que l'on n'ait pas besoin de répéter la même logique encore et encore. La nature singleton du service permet de partager le même élément de données sur plusieurs contrôleurs, directives et même d'autres services.
Un service peut dépendre d'un ensemble d'autres services pour effectuer sa tâche. Disons qu'un service nommé dépend des services B, C et D pour effectuer sa tâche. Lors du test du service A, les dépendances B, C et D doivent être remplacées par des simulations.
Nous nous moquons généralement de toutes les dépendances, à l'exception de certains services d'utilité comme $ Rootscope et $ parse. Nous créons des espions sur les méthodes qui doivent être inspectées dans les tests (en jasmin, les simulations sont appelées espions) en utilisant jasmine.createSpy () qui renverra une toute nouvelle fonction.
Considérons le service suivant:
angular<span>.module('services', []) </span> <span>.service('sampleSvc', ['$window', 'modalSvc', function($<span>window, modalSvc</span>){ </span> <span>this.showDialog = function(message<span>, title</span>){ </span> <span>if(title){ </span> modalSvc<span>.showModalDialog({ </span> <span>title: title, </span> <span>message: message </span> <span>}); </span> <span>} else { </span> $<span>window.alert(message); </span> <span>} </span> <span>}; </span> <span>}]);</span>
Ce service n'a qu'une seule méthode (showDialog). Selon la valeur de l'entrée que cette méthode reçoit, elle appelle l'un des deux services qui y sont injectés sous forme de dépendances ($ Window ou Modalsvc).
Pour tester des échantillons VC, nous devons se moquer des deux services dépendants, charger le module angulaire qui contient notre service et obtenir des références à tous les objets:
<span>var mockWindow, mockModalSvc, sampleSvcObj; </span><span>beforeEach(function(){ </span> <span>module(function($provide){ </span> $provide<span>.service('$window', function(){ </span> <span>this.alert= jasmine.createSpy('alert'); </span> <span>}); </span> $provide<span>.service('modalSvc', function(){ </span> <span>this.showModalDialog = jasmine.createSpy('showModalDialog'); </span> <span>}); </span> <span>}); </span> <span>module('services'); </span><span>}); </span> <span>beforeEach(inject(function($<span>window, modalSvc, sampleSvc</span>){ </span> mockWindow<span>=$window; </span> mockModalSvc<span>=modalSvc; </span> sampleSvcObj<span>=sampleSvc; </span><span>}));</span>
Maintenant, nous pouvons tester le comportement de la méthode showDialog. Les deux cas de test que nous pouvons écrire pour la méthode sont les suivants:
l'extrait suivant montre ces tests:
<span>it('should show alert when title is not passed into showDialog', function(){ </span> <span>var message="Some message"; </span> sampleSvcObj<span>.showDialog(message); </span> <span>expect(mockWindow.alert).toHaveBeenCalledWith(message); </span> <span>expect(mockModalSvc.showModalDialog).not.toHaveBeenCalled(); </span><span>}); </span> <span>it('should show modal when title is passed into showDialog', function(){ </span> <span>var message="Some message"; </span> <span>var title="Some title"; </span> sampleSvcObj<span>.showDialog(message, title); </span> <span>expect(mockModalSvc.showModalDialog).toHaveBeenCalledWith({ </span> <span>message: message, </span> <span>title: title </span> <span>}); </span> <span>expect(mockWindow.alert).not.toHaveBeenCalled(); </span><span>});</span>
Cette méthode n'a pas beaucoup de logique à tester, tandis que les services dans les applications Web typiques contiendraient normalement beaucoup de fonctionnalités. Vous pouvez utiliser la technique démontrée dans cette astuce pour se moquer et obtenir les références aux services. Les tests de service doivent couvrir tous les scénarios possibles qui ont été supposés lors de la rédaction du service.
Les usines et les valeurs peuvent également être testées en utilisant la même technique.
Le processus de configuration pour tester un contrôleur est très différent de celui d'un service. En effet, les contrôleurs ne sont pas injectables, ils sont plutôt instanciés automatiquement lorsqu'une route se charge ou, une directive NG-contrôleur est compilée. Comme nous n'avons pas les vues de chargement dans les tests, nous devons instancier manuellement le contrôleur testé.
Comme les contrôleurs sont généralement liés à une vue, le comportement des méthodes dans les contrôleurs dépend des vues. De plus, certains objets supplémentaires peuvent être ajoutés à la portée une fois la vue compilée. L'un des exemples les plus courants de ceci est un objet de forme. Afin de faire fonctionner les tests comme prévu, ces objets doivent être créés manuellement et ajoutés au contrôleur.
Un contrôleur peut être de l'un des types suivants:
Si vous n'êtes pas sûr de la différence, vous pouvez en savoir plus ici. Quoi qu'il en soit, nous discuterons de ces deux cas.
Considérez le contrôleur suivant:
angular<span>.module('services', []) </span> <span>.service('sampleSvc', ['$window', 'modalSvc', function($<span>window, modalSvc</span>){ </span> <span>this.showDialog = function(message<span>, title</span>){ </span> <span>if(title){ </span> modalSvc<span>.showModalDialog({ </span> <span>title: title, </span> <span>message: message </span> <span>}); </span> <span>} else { </span> $<span>window.alert(message); </span> <span>} </span> <span>}; </span> <span>}]);</span>
Pour tester ce contrôleur, nous devons créer une instance du contrôleur en transmettant un objet Scope $ et un objet moqué du service (DataSVC). Comme le service contient une méthode asynchrone, nous devons nous moquer de celle en utilisant la technique de promesse moquerie que j'ai décrite dans un article précédent.
Le extrait suivant se moque du service DataSVC:
<span>var mockWindow, mockModalSvc, sampleSvcObj; </span><span>beforeEach(function(){ </span> <span>module(function($provide){ </span> $provide<span>.service('$window', function(){ </span> <span>this.alert= jasmine.createSpy('alert'); </span> <span>}); </span> $provide<span>.service('modalSvc', function(){ </span> <span>this.showModalDialog = jasmine.createSpy('showModalDialog'); </span> <span>}); </span> <span>}); </span> <span>module('services'); </span><span>}); </span> <span>beforeEach(inject(function($<span>window, modalSvc, sampleSvc</span>){ </span> mockWindow<span>=$window; </span> mockModalSvc<span>=modalSvc; </span> sampleSvcObj<span>=sampleSvc; </span><span>}));</span>
Nous pouvons ensuite créer une nouvelle portée pour le contrôleur à l'aide de la méthode $ Rootscope. $ Nouvelle. Après avoir créé une instance du contrôleur, nous avons tous les champs et méthodes sur cette nouvelle portée $.
<span>it('should show alert when title is not passed into showDialog', function(){ </span> <span>var message="Some message"; </span> sampleSvcObj<span>.showDialog(message); </span> <span>expect(mockWindow.alert).toHaveBeenCalledWith(message); </span> <span>expect(mockModalSvc.showModalDialog).not.toHaveBeenCalled(); </span><span>}); </span> <span>it('should show modal when title is passed into showDialog', function(){ </span> <span>var message="Some message"; </span> <span>var title="Some title"; </span> sampleSvcObj<span>.showDialog(message, title); </span> <span>expect(mockModalSvc.showModalDialog).toHaveBeenCalledWith({ </span> <span>message: message, </span> <span>title: title </span> <span>}); </span> <span>expect(mockWindow.alert).not.toHaveBeenCalled(); </span><span>});</span>
Comme le contrôleur ajoute un champ et une méthode à $ SCOPE, nous pouvons vérifier s'ils sont définis sur les bonnes valeurs et si les méthodes ont la logique correcte. Le contrôleur d'échantillon ci-dessus ajoute une expression régulière pour vérifier un nombre valide. Ajoutons une spécification pour tester le comportement de l'expression régulière:
angular<span>.module('controllers',[]) </span> <span>.controller('FirstController', ['$scope','dataSvc', function($scope<span>, dataSvc</span>) { </span> $scope<span>.saveData = function () { </span> dataSvc<span>.save($scope.bookDetails).then(function (result) { </span> $scope<span>.bookDetails = {}; </span> $scope<span>.bookForm.$setPristine(); </span> <span>}); </span> <span>}; </span> $scope<span>.numberPattern = <span>/<span>^\d*$</span>/</span>; </span> <span>}]);</span>
Si un contrôleur initialise des objets avec des valeurs par défaut, nous pouvons vérifier leurs valeurs dans la spécification
Pour tester la méthode SaveData, nous devons définir certaines valeurs pour les objets BookDetails et Bookformes. Ces objets seraient liés aux éléments d'interface utilisateur, ils sont donc créés au moment de l'exécution lorsque la vue est compilée. Comme déjà mentionné, nous devons les initialiser manuellement avec certaines valeurs avant d'appeler la méthode SaveData.
l'extrait suivant teste cette méthode:
<span>module(function($provide){ </span> $provide<span>.factory('dataSvc', ['$q', function($q) </span> <span>function save(data){ </span> <span>if(passPromise){ </span> <span>return $q.when(); </span> <span>} else { </span> <span>return $q.reject(); </span> <span>} </span> <span>} </span> <span>return{ </span> <span>save: save </span> <span>}; </span> <span>}]); </span><span>});</span>
Tester un contrôleur qui utilise le contrôleur comme syntaxe est plus facile que de tester celui en utilisant $ Scope. Dans ce cas, une instance du contrôleur joue le rôle d'un modèle. Par conséquent, toutes les actions et objets sont disponibles sur cette instance.
Considérez le contrôleur suivant:
<span>beforeEach(inject(function($rootScope<span>, $controller, dataSvc</span>){ </span> scope<span>=$rootScope.$new(); </span> mockDataSvc<span>=dataSvc; </span> <span>spyOn(mockDataSvc,'save').andCallThrough(); </span> firstController <span>= $controller('FirstController', { </span> <span>$scope: scope, </span> <span>dataSvc: mockDataSvc </span> <span>}); </span><span>}));</span>
Le processus d'invoquer ce contrôleur est similaire au processus discuté précédemment. La seule différence est que nous n'avons pas besoin de créer une portée $.
<span>it('should have assigned right pattern to numberPattern', function(){ </span> <span>expect(scope.numberPattern).toBeDefined(); </span> <span>expect(scope.numberPattern.test("100")).toBe(true); </span> <span>expect(scope.numberPattern.test("100aa")).toBe(false); </span><span>});</span>
Comme tous les membres et méthodes du contrôleur sont ajoutés à cette instance, nous pouvons y accéder en utilisant la référence d'instance.
l'extrait suivant teste le champ NumberPattern ajouté au contrôleur ci-dessus:
<span>it('should call save method on dataSvc on calling saveData', function(){ </span> scope<span>.bookDetails = { </span> <span>bookId: 1, </span> <span>name: "Mastering Web application development using AngularJS", </span> <span>author:"Peter and Pawel" </span> <span>}; </span> scope<span>.bookForm = { </span> <span>$setPristine: jasmine.createSpy('$setPristine') </span> <span>}; </span> passPromise <span>= true; </span> scope<span>.saveData(); </span> scope<span>.$digest(); </span> <span>expect(mockDataSvc.save).toHaveBeenCalled(); </span> <span>expect(scope.bookDetails).toEqual({}); </span> <span>expect(scope.bookForm.$setPristine).toHaveBeenCalled(); </span><span>});</span>
Les affirmations de la méthode SaveData restent les mêmes. La seule différence dans cette approche est la façon dont nous initialisons les valeurs aux objets de bookdetails et de forme de livres.
l'extrait suivant montre la spécification:
angular<span>.module('services', []) </span> <span>.service('sampleSvc', ['$window', 'modalSvc', function($<span>window, modalSvc</span>){ </span> <span>this.showDialog = function(message<span>, title</span>){ </span> <span>if(title){ </span> modalSvc<span>.showModalDialog({ </span> <span>title: title, </span> <span>message: message </span> <span>}); </span> <span>} else { </span> $<span>window.alert(message); </span> <span>} </span> <span>}; </span> <span>}]);</span>
sont utilisés pour exposer une API pour la configuration à l'échelle de l'application qui doit être effectuée avant le début de l'application. Une fois la phase de configuration d'une application AngularJS terminée, l'interaction avec les fournisseurs est interdite. Par conséquent, les fournisseurs ne sont accessibles que dans les blocs de configuration ou dans d'autres blocs de fournisseur. Nous ne pouvons pas obtenir une instance de fournisseur à l'aide d'un bloc d'injection, nous devons plutôt passer un rappel au bloc du module.
Considérons le fournisseur suivant qui dépend d'une constante (appcconstants) un deuxième fournisseur (un autre proviseur):
<span>var mockWindow, mockModalSvc, sampleSvcObj; </span><span>beforeEach(function(){ </span> <span>module(function($provide){ </span> $provide<span>.service('$window', function(){ </span> <span>this.alert= jasmine.createSpy('alert'); </span> <span>}); </span> $provide<span>.service('modalSvc', function(){ </span> <span>this.showModalDialog = jasmine.createSpy('showModalDialog'); </span> <span>}); </span> <span>}); </span> <span>module('services'); </span><span>}); </span> <span>beforeEach(inject(function($<span>window, modalSvc, sampleSvc</span>){ </span> mockWindow<span>=$window; </span> mockModalSvc<span>=modalSvc; </span> sampleSvcObj<span>=sampleSvc; </span><span>}));</span>
Pour tester cela, nous devons d'abord se moquer des dépendances. Vous pouvez voir comment faire cela dans l'exemple de code.
Avant de tester le fournisseur, nous devons nous assurer que le module est chargé et prêt. Dans les tests, le chargement des modules est différé jusqu'à ce qu'un bloc d'injecte soit exécuté ou que le premier test soit exécuté. Dans quelques projets, j'ai vu des tests qui utilisent un premier test vide pour charger le module. Je ne suis pas un fan de cette approche car le test ne fait rien et ajoute un décompte à votre nombre total de tests. Au lieu de cela, j'utilise un bloc d'injection vide pour faire charger les modules.
l'extrait suivant obtient les références et charge les modules:
<span>it('should show alert when title is not passed into showDialog', function(){ </span> <span>var message="Some message"; </span> sampleSvcObj<span>.showDialog(message); </span> <span>expect(mockWindow.alert).toHaveBeenCalledWith(message); </span> <span>expect(mockModalSvc.showModalDialog).not.toHaveBeenCalled(); </span><span>}); </span> <span>it('should show modal when title is passed into showDialog', function(){ </span> <span>var message="Some message"; </span> <span>var title="Some title"; </span> sampleSvcObj<span>.showDialog(message, title); </span> <span>expect(mockModalSvc.showModalDialog).toHaveBeenCalledWith({ </span> <span>message: message, </span> <span>title: title </span> <span>}); </span> <span>expect(mockWindow.alert).not.toHaveBeenCalled(); </span><span>});</span>
Maintenant que nous avons toutes les références, nous pouvons appeler des méthodes définies dans les fournisseurs et les tester:
angular<span>.module('controllers',[]) </span> <span>.controller('FirstController', ['$scope','dataSvc', function($scope<span>, dataSvc</span>) { </span> $scope<span>.saveData = function () { </span> dataSvc<span>.save($scope.bookDetails).then(function (result) { </span> $scope<span>.bookDetails = {}; </span> $scope<span>.bookForm.$setPristine(); </span> <span>}); </span> <span>}; </span> $scope<span>.numberPattern = <span>/<span>^\d*$</span>/</span>; </span> <span>}]);</span>
Les tests unitaires deviennent parfois délicats, mais cela vaut la peine de passer du temps car il garantit l'exactitude de l'application. AngularJs facilite le test unitaire du code écrit à l'aide du cadre. J'espère que cet article vous donnera une idée suffisante pour développer et améliorer les tests de vos applications. Dans un futur article, nous continuerons à examiner comment tester d'autres pièces de votre code.
Les tests unitaires sont un aspect crucial du développement AngularJS. Il aide à vérifier la fonctionnalité des composants individuels, tels que les services, les contrôleurs et les fournisseurs, isolément. Cela garantit que chaque composant fonctionne comme prévu avant d'être intégrés dans l'application plus large. Les tests unitaires peuvent aider à identifier les bogues au début du processus de développement, ce qui les rend plus faciles et moins coûteux à corriger. Il aide également à maintenir la qualité du code et à améliorer la fiabilité globale de l'application.
La configuration d'un environnement de test pour AngularJS implique plusieurs étapes. Tout d'abord, vous devez installer les outils de test nécessaires tels que le jasmin et le karma. Jasmine est un cadre de développement axé sur le comportement pour tester le code JavaScript, tandis que Karma est un coureur de test qui exécute des tests dans un véritable navigateur. Après avoir installé ces outils, vous pouvez écrire des cas de test dans des fichiers de test séparés et les exécuter à l'aide de Karma.
Les services AngularJS peuvent être testés en les injectant dans un test et se moquer de leurs dépendances. Jasmine fournit une fonction appelée «Spyon» qui vous permet de créer une fonction simulée et de suivre ses appels. Vous pouvez utiliser cette fonction pour se moquer des dépendances du service et l'isoler pour les tests. Après avoir mis en place la simulation, vous pouvez appeler les méthodes du service et utiliser la fonction «attendre» de Jasmine pour vérifier leur sortie.
Tester les contrôleurs angularjs impliquent la création d'une instance du contrôleur et testant ses méthodes et propriétés. Vous pouvez créer une instance du contrôleur à l'aide du service de contrôleur $ fourni par AngularJS. Après avoir créé l'instance, vous pouvez appeler ses méthodes et vérifier leurs effets sur la portée du contrôleur. Vous pouvez également tester l'interaction du contrôleur avec les services en se moquant des services et en vérifiant les appels vers leurs méthodes.
Tester les fournisseurs angularjs est similaire aux services de test. Vous pouvez injecter le fournisseur dans un test, se moquer de ses dépendances et tester ses méthodes. Cependant, les fournisseurs ont une méthode spéciale appelée «$ get» qui renvoie l'instance du service. Cette méthode peut être testée séparément en l'appelant et en vérifiant la valeur renvoyée.
L'injection de dépendance est une caractéristique clé d'AngularJS qui vous permet d'injecter dépendances en composants. Dans les tests, vous pouvez utiliser cette fonctionnalité pour injecter des versions simulées des dépendances dans le composant testé. Cela vous permet d'isoler le composant et de le tester indépendamment de ses dépendances.
Les opérations asynchrones dans les tests angularjs peuvent être manipulées en utilisant le service $ q et la fonction «terminée» fournie par Jasmine. Le service $ Q vous permet de créer des promesses qui peuvent être résolues ou rejetées dans le test. La fonction «terminée» peut être appelée pour signaler que l'opération asynchrone s'est terminée.
Les directives angularjs Angularjs impliquent de créer une instance de la directive et de tester son comportement . Vous pouvez créer une instance de la directive à l'aide du service $ compilé fourni par AngularJS. Après avoir créé l'instance, vous pouvez le manipuler en utilisant des méthodes de type jQuery et vérifier ses effets sur la portée.
Dépendances externes dans les tests angularjs peut être traité en se moquant d'eux. Vous pouvez créer une version simulée de la dépendance et l'injecter dans le composant testé. Cela vous permet de contrôler le comportement de la dépendance et d'isoler le composant pour les tests.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!