Comment simulez-vous les directives pour permettre les tests unitaires de la directive de niveau supérieur?

Dans notre application, nous avons plusieurs couches de directives nestedes. J’essaie d’écrire des tests unitaires pour les directives de haut niveau. Je me suis moqué des choses dont la directive elle-même a besoin, mais maintenant je rencontre des erreurs des directives de niveau inférieur. Dans mes tests unitaires pour la directive de niveau supérieur, je ne veux pas avoir à me soucier de ce qui se passe dans les directives de niveau inférieur. Je veux simplement me moquer de la directive de niveau inférieur et essentiellement ne rien faire pour que je puisse tester la directive de haut niveau de manière isolée.

J’ai essayé de remplacer la définition de la directive en faisant quelque chose comme ceci:

angular.module("myModule").directive("myLowerLevelDirective", function() { return { link: function(scope, element, attrs) { //do nothing } } }); 

Cependant, cela ne le remplace pas, il ne fait que l’exécuter en plus de la directive réelle. Comment puis-je empêcher ces directives de niveau inférieur de faire quelque chose dans mon test unitaire pour la directive de niveau supérieur?

En raison de la mise en œuvre de l’enregistrement de la directive, il ne semble pas possible de remplacer une directive existante par une directive simulée.

Cependant, vous disposez de plusieurs moyens pour tester votre directive de niveau supérieur sans interférence des directives de niveau inférieur:

1) N’utilisez pas de directive de niveau inférieur dans votre modèle de test unitaire :

Si votre directive de niveau inférieur n’est pas ajoutée par votre directive de niveau supérieur, utilisez dans votre test un modèle avec uniquement vous-même la directive de niveau:

 var html = "
"; $comstack(html)(scope);

Donc, la directive de niveau inférieur n’interférera pas.

2) Utilisez un service dans votre implémentation de directive:

Vous pouvez fournir la fonction de liaison de niveau inférieur par un service:

 angular.module("myModule").directive("myLowerLevelDirective", function(myService) { return { link: myService.lowerLevelDirectiveLinkingFunction } }); 

Ensuite, vous pouvez simuler ce service dans votre test unitaire pour éviter toute interférence avec votre directive de niveau supérieur. Ce service peut même fournir l’object directive complet si nécessaire.

3) Vous pouvez remplacer votre directive de niveau inférieur par une directive de terminal :

 angular.module("myModule").directive("myLowerLevelDirective", function(myService) { return { priority: 100000, terminal: true, link: function() { // do nothing } } }); 

Avec l’option terminal et une priorité plus élevée, votre véritable directive de niveau inférieur ne sera pas exécutée. Plus d’informations dans la directive doc .

Voyez comment cela fonctionne dans ce Plunker .

Les directives ne sont que des fabriques, la meilleure façon de le faire est de simuler la fabrique de la directive en utilisant la fonction de module , généralement dans le bloc beforeEach . En supposant que vous ayez une directive nommée do-something utilisée par une directive appelée do-something, sinon vous vous moqueriez de cela:

 beforeEach(module('yourapp/test', function($provide){ $provide.factory('doSomethingDirective', function(){ return {}; }); })); // Or using the shorthand sytax beforeEach(module('yourapp/test', { doSomethingDirective: {} )); 

Ensuite, la directive sera remplacée lorsque le modèle est compilé dans votre test

 inject(function($comstack, $rootScope){ $comstack('', $rootScope.$new()); }); 

Notez que vous devez append le suffixe “Directive” au nom car le compilateur le fait en interne: https://github.com/angular/angular.js/blob/821ed310a75719765448e8b15e3a56f0389107a5/src/ng/comstack.js#L530

La façon propre de se moquer d’une directive est avec $comstackProvider

 beforeEach(module('plunker', function($comstackProvider){ $comstackProvider.directive('d1', function(){ var def = { priority: 100, terminal: true, ressortingct:'EAC', template:'
this is a mock
', }; return def; }); }));

Vous devez vous assurer que le simulacre obtient une priorité plus élevée que la directive que vous moquez et que le simulacre est terminal afin que la directive originale ne soit pas compilée.

 priority: 100, terminal: true, 

Le résultat ressemblerait à ceci:

Compte tenu de cette directive:

 var app = angular.module('plunker', []); app.directive('d1', function(){ var def = { ressortingct: 'E', template:'
d1
' } return def; });

Vous pouvez vous moquer comme ça:

 describe('testing with a mock', function() { var $scope = null; var el = null; beforeEach(module('plunker', function($comstackProvider){ $comstackProvider.directive('d1', function(){ var def = { priority: 9999, terminal: true, ressortingct:'EAC', template:'
this is a mock
', }; return def; }); })); beforeEach(inject(function($rootScope, $comstack) { $scope = $rootScope.$new(); el = $comstack('
')($scope); })); it('should contain mocked element', function() { expect(el.find('.mock').length).toBe(1); }); });

Quelques autres choses:

  • Lorsque vous créez votre maquette, vous devez déterminer si vous devez ou non replace:true et / ou un template . Par exemple, si vous vous moquez de ng-src pour empêcher les appels au backend, vous ne voulez pas replace:true et vous ne voulez pas spécifier de template . Mais si vous vous moquez de quelque chose de visuel, vous voudrez peut-être.

  • Si vous définissez une priorité supérieure à 100, les atsortingbuts de vos simulations ne seront pas interpolés. Voir $ comstackr le code source . Par exemple, si vous vous moquez de ng-src et définissez la priority:101 , alors vous vous retrouverez avec ng-src="{{variable}}" non ng-src="interpolated-value" sur votre maquette.

Voici un plongeur avec tout. Merci à @trodrigues de m’avoir orienté dans la bonne direction.

Voici quelques doc qui explique plus, consultez la section “Blocs de configuration”. Merci à @ebelanger!

Vous pouvez modifier vos modèles dans $templateCache pour supprimer les directives de niveau inférieur:

 beforeEach(angular.mock.inject(function ($templateCache) { $templateCache.put('path/to/template.html', '
'); }));

Étant obligé de penser à cela plus moi-même, j’ai trouvé une solution qui répond à nos besoins. Toutes nos directives sont des atsortingbuts, alors j’ai créé une directive atsortingbuteRemover à utiliser lors des tests unitaires. Cela ressemble à ceci:

 angular.module("myModule").directive("atsortingbuteRemover", function() { return { priority: -1, //make sure this runs last comstack: function(element, attrs) { var atsortingbutesToRemove = attrs.atsortingbuteRemover.split(","); angular.forEach(atsortingbutesToRemove, function(currAtsortingbuteToRemove) { element.find("div[" + currAtsortingbuteToRemove + "]").removeAttr(currAtsortingbuteToRemove); }); } } }); 

Ensuite, le code HTML de la directive que je teste ressemble à ceci:

 

Ainsi, lorsque my-higher-level-directive est compilé, l’ atsortingbute-remover aura déjà supprimé les atsortingbuts des directives de niveau inférieur et je n’ai donc pas à m’inquiéter de ce qu’il fait.

Il existe probablement un moyen plus robuste de le faire pour toutes sortes de directives (pas seulement celles des atsortingbuts) et je ne suis pas sûr que cela fonctionne uniquement si vous utilisez le JQLite intégré, mais cela fonctionne pour ce dont nous avons besoin.

J’ai tellement aimé la réponse de Sylvain que je devais en faire une fonction d’assistance. Le plus souvent, ce dont j’ai besoin est de supprimer une directive enfant pour pouvoir comstackr et tester la directive de conteneur parent sans ses dépendances. Donc, cette aide nous permet de le faire:

 function killDirective(directiveName) { angular.mock.module(function($comstackProvider) { $comstackProvider.directive(directiveName, function() { return { priority: 9999999, terminal: true } }); }); } 

Avec cela, vous pouvez désactiver complètement une directive en l’exécutant avant la création de l’injecteur:

 killDirective('myLowerLevelDirective'); 

Voici une autre petite idée. Il suffit de mettre ce code dans les aides de jasmine (script de café)

 window.mockDirective = (name, factoryFunction) -> mockModule = angular.module('mocks.directives', ['ng']) mockModule.directive(name, factoryFunction) module ($provide) -> factoryObject = angular.injector([mockModule.name]).get("#{name}Directive") $provide.factory "#{name}Directive", -> factoryObject null 

Et l’utiliser:

 beforeEach mockDirective, "myLowerLevelDirective", -> link: (scope, element) -> 

Cela supprimera complètement toutes les autres implémentations d’une directive donnée, donnant un access complet aux arguments de test transmis à la directive. Par exemple, la directive d’alerte mm.foundation peut être simulée avec:

 beforeEach mockDirective 'alert', -> scope: type: '=' 

et ensuite testé:

 expect(element.find('alert').data('$isolateScopeNoTemplate').type).toEqual