So testen Sie die Richtlinie für isolierte Bereiche in AngularJS

81

Was ist ein guter Weg, um isolierte Bereiche in AngularJS zu testen?

JSFiddle zeigt Unit-Test

Direktiven-Snippet

    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);
        });
    }

Ich möchte sicherstellen, dass die Richtlinie auf Änderungen wartet - dies funktioniert nicht mit einem isolierten Bereich:

    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: Ich habe es zum Laufen gebracht, indem ich überprüft habe, ob die erwarteten Beobachter zum untergeordneten Bereich hinzugefügt wurden, aber es ist sehr spröde und verwendet die Accessoren wahrscheinlich auf undokumentierte Weise (auch bekannt als Änderungen vorbehalten!).

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

UPDATE 2: Wie gesagt, das ist spröde! Die Idee funktioniert immer noch, aber in neueren Versionen von AngularJS hat sich der Accessor von geändert scope()zu isolateScope():

//this is STILL super brittle, is there a better way!?
elm = compile(validHTML)(scope);                       
expect(elm.isolateScope().$$watchers[0].exp).toBe('name');
daniellmb
quelle
Haben Sie einen Weg gefunden, die Spionage einzurichten?
Tusharmath
@Tushar nicht wirklich, wie zuvor gibt es eine Möglichkeit, es zum Laufen zu bringen, aber es kann ohne vorherige Ankündigung geändert werden. Verwenden Sie es daher auf eigenes Risiko.
Daniellmb

Antworten:

102

Siehe Winkelelement-API-Dokumente . Wenn Sie element.scope () verwenden, erhalten Sie den Bereich des Elements, den Sie in der Eigenschaft scope Ihrer Direktive definiert haben. Wenn Sie element.isolateScope () verwenden, erhalten Sie den gesamten isolierten Bereich. Wenn Ihre Direktive beispielsweise ungefähr so ​​aussieht:

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

Dann wird der Aufruf von element.scope () in Ihrem Test zurückgegeben

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

Wenn Sie jedoch element.isolateScope () aufrufen, erhalten Sie

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

Dies gilt ab Winkel 1.2.2 oder 1.2.3, nicht genau sicher. In früheren Versionen hatten Sie nur element.scope ().

Yair Tavor
quelle
1
v1.2.3 feat (jqLite): exponate isolateScope () getter ähnlich wie scope () github.com/angular/angular.js/commit/…
daniellmb
1
aber wo spionieren Sie die $ watch-Methode aus?
Tusharmath
1
Sie könnten die Funktion, die auf der $ watch ausgeführt wird, verfügbar machen und sie dann ausspionieren. Setzen Sie in der Direktive "scope.myfunc = function () ..." und in $ watch "$ scope. $ Watch ('myName', scope.myfunc);". Jetzt im Test können Sie myFunc aus dem isolierten Bereich holen und es ausspionieren.
Yair Tavor
22
Funktioniert bei mir nicht element.isolateScope()kehrt zurück undefined. Und element.scope()gibt einen Bereich zurück, der nicht alle Inhalte enthält, die ich in meinen Bereich aufgenommen habe.
McV
4
@mcv Ich fand, ich musste tunelement.children().isolateScope()
Will Keeling
11

Sie können var isolateScope = myDirectiveElement.scope()den isolierten Bereich abrufen.

Sie müssen nicht wirklich testen, dass $ watch aufgerufen wurde. Das ist mehr das Testen von AngularJs als das Testen Ihrer App. Aber ich denke, es ist nur ein Beispiel für die Frage.

Andrew Joslin
quelle
2
Ich bin mir nicht sicher, ob ich damit einverstanden bin, dass es "Winkel testen" ist. Ich teste nicht, dass $ watch funktioniert, sondern einfach, dass die Direktive eine Eigenschaft ist, die mit Winkel "verdrahtet" ist.
Daniellmb
1
Auch daniellmb, der Weg, dies zu testen, wäre, Ihre greetFunktion aufzudecken und das auszuspionieren und zu überprüfen, ob das aufgerufen wird - nicht die $ watch.
Andrew Joslin
Richtig, dies ist ein erfundenes Beispiel, aber ich war interessiert, ob es eine saubere Möglichkeit gibt, den isolierten Bereich zu testen. In diesem Fall würde es nicht funktionieren, die Kapselung zu unterbrechen und Methoden in den Bereich einzufügen, da es keinen Haken gibt, um den Spion hinzuzufügen, bevor er aufgerufen wird.
Daniellmb
@AndyJoslin, Warum aus Neugier überhaupt eine isolateScopeVariable erstellen ? Siehe Angs Kommentar zu diesem Egghead-Video ( Egghead.io/lessons/angularjs-unit-testing-directive-scope ): Ab Angular 1.2 muss element.isolateScope()anstelle von element.scope() code.angularjs.org/1.2
Danger14
1

Verschieben Sie die Logik auf einen separaten Controller, dh:

//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);

und testen Sie letzteres wie jeden Controller - übergeben Sie das Bereichsobjekt direkt ( ein Beispiel finden Sie im Abschnitt Controller hier ).

Wenn Sie in Ihrem Test $ watch auslösen müssen, gehen Sie wie folgt vor:

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
  });
});
Nikita
quelle
0

Ich bin mir nicht sicher, ob es mit isoliertem Umfang möglich ist (obwohl ich hoffe, dass mich jemand als falsch erweist). Der Isolationsbereich, der in der Direktive erstellt wird, ist isoliert, sodass sich die $ watch-Methode in der Direktive von dem Bereich unterscheidet, den Sie im Komponententest ausspionieren. Wenn Sie scope: {} in scope: true ändern, erbt der Direktivenbereich prototypisch und Ihre Tests sollten bestanden werden.

Ich denke, dies ist nicht die idealste Lösung, da manchmal (häufig) das Isolieren des Bereichs eine gute Sache ist.

UnicodeSnowman
quelle