web-dev-qa-db-fra.com

Comment tester à l'unité la directive de portée isolée dans AngularJS

Quel est le bon moyen de tester à l'unité le périmètre isolé dans AngularJS?

JSFiddle montrant le test unitaire

Extrait de directive

    scope: {name: '=myGreet'},
    link: function (scope, element, attrs) {
        //show the initial state
        greet(element, scope[attrs.myGreet]);

        //listen for changes in the model
        scope.$watch(attrs.myGreet, function (name) {
            greet(element, name);
        });
    }

Je veux m'assurer que la directive est à l'écoute des modifications - cela ( ne fonctionne pas avec une portée isolée:

    it('should watch for changes in the model', function () {
        var Elm;
        //arrange
        spyOn(scope, '$watch');
        //act
        Elm = compile(validHTML)(scope);
        //assert
        expect(scope.$watch.callCount).toBe(1);
        expect(scope.$watch).toHaveBeenCalledWith('name', jasmine.any(Function));
    });

UPDATE: Je l'ai fait fonctionner en vérifiant si les observateurs attendus ont été ajoutés à la portée des enfants, mais ils sont très fragiles et utilisent probablement les accesseurs dans un document non documenté. manière (aka sujet à changer sans préavis!).

//this is super brittle, is there a better way!?
Elm = compile(validHTML)(scope);
expect(Elm.scope().$$watchers[0].exp).toBe('name');

UPDATE 2: Comme je l’ai dit, c’est cassant! L'idée fonctionne toujours, mais dans les versions plus récentes d'AngularJS, l'accesseur a changé de scope() à isolateScope():

//this is STILL super brittle, is there a better way!?
Elm = compile(validHTML)(scope);                       
expect(Elm.isolateScope().$$watchers[0].exp).toBe('name');
81
daniellmb

Voir éléments angulaires api docs . Si vous utilisez element.scope () , vous obtenez la portée de l'élément que vous avez définie dans la propriété scope de votre directive. Si vous utilisez element.isolateScope () , vous obtenez la totalité de la portée isolée. Par exemple, si votre directive ressemble à quelque chose comme ceci:

scope : {
 myScopeThingy : '='
},
controller : function($scope){
 $scope.myIsolatedThingy = 'some value';
}

Ensuite, appeler element.scope () dans votre test renverra

{ myScopeThingy : 'whatever value this is bound to' }

Mais si vous appelez element.isolateScope (), vous aurez

{ 
  myScopeThingy : 'whatever value this is bound to', 
  myIsolatedThingy : 'some value'
}

Ceci est vrai à partir de angular 1.2.2 ou 1.2.3, pas vraiment sûr. Dans les versions précédentes, vous n'aviez que element.scope ().

101
Yair Tavor

Vous pouvez utiliser var isolateScope = myDirectiveElement.scope() pour obtenir la portée d'isolement.

Vous n'avez pas vraiment besoin de tester que $ watch s'appelle bien .. c'est plus de tester des angularjs que de tester votre application. Mais je suppose que ce n'est qu'un exemple de la question.

11
Andrew Joslin

déplacez la logique vers un contrôleur séparé, c'est-à-dire:

//will get your isolate scope
function MyCtrl($scope)
{
  //non-DOM manipulating ctrl logic here
}
app.controller(MyCtrl);

function MyDirective()
{
  return {
    scope     : {},
    controller: MyCtrl,
    link      : function (scope, element, attrs)
    {
      //moved non-DOM manipulating logic to ctrl
    }
  }
}
app.directive('myDirective', MyDirective);

et testez ce dernier comme n'importe quel contrôleur - en passant directement l'objet de la portée (voir Contrôleurs section ici pour un exemple).

si vous avez besoin de déclencher $ watch dans votre test, faites:

describe('MyCtrl test', function ()
{
  var $rootScope, $controller, $scope;

  beforeEach(function ()
  {
    inject(function (_$rootScope_, _$controller_)
    {
      // The injector unwraps the underscores (_) from around the parameter names when matching
      $rootScope = _$rootScope_;
      $controller = _$controller_;
    });

    $scope = $rootScope.$new({});
    $scope.foo = {x: 1}; //initial scope state as desired
    $controller(MyCtrl, {$scope: $scope}); //or by name as 'MyCtrl'
  });

  it('test scope property altered on $digest', function ()
  {
    $scope.$digest(); //trigger $watch
    expect($scope.foo.x).toEqual(1); //or whatever
  });
});
1
Nikita

Je ne suis pas sûr que ce soit possible avec une portée isolée (bien que j'espère que quelqu'un me prouve le contraire). La portée d'isolement créée dans la directive est, ainsi, isolée. Par conséquent, la méthode $ watch de la directive est différente de la portée que vous espionnez dans le test unitaire. Si vous modifiez la portée: {} en portée: true, la portée de la directive héritera du prototype et vos tests devront réussir.

Je suppose que ce n’est pas la solution idéale, car parfois (souvent), isoler la portée est une bonne chose.

0
UnicodeSnowman