Jak przeprowadzić testy jednostkowe dyrektywy zakresu izolowanego w AngularJS

81

Jaki jest dobry sposób testowania jednostkowego izolowanego zakresu w AngularJS

JSFiddle pokazujący test jednostkowy

Fragment dyrektywy

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

Chcę mieć pewność, że dyrektywa nasłuchuje zmian - to nie działa w odosobnionym zakresie:

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

AKTUALIZACJA: uruchomiłem to, sprawdzając, czy oczekiwani obserwatorzy zostali dodani do zakresu podrzędnego, ale jest bardzo kruchy i prawdopodobnie używa akcesorów w nieudokumentowany sposób (aka może ulec zmianie bez powiadomienia!).

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

UPDATE 2: Jak wspomniałem, jest kruchy! Pomysł nadal działa, ale w nowszych wersjach AngularJS akcesor zmienił się z scope()na isolateScope():

//this is STILL super brittle, is there a better way!?
elm = compile(validHTML)(scope);                       
expect(elm.isolateScope().$$watchers[0].exp).toBe('name');
daniellmb
źródło
Czy znalazłeś sposób na skonfigurowanie szpiegostwa?
tusharmath
@Tushar niezupełnie, tak jak wcześniej, istnieje sposób, aby to działało, ale może ulec zmianie bez powiadomienia, więc używaj na własne ryzyko.
daniellmb

Odpowiedzi:

102

Zobacz dokumentację api elementu kątowego . Jeśli używasz element.scope () , otrzymasz zakres elementu, który zdefiniowałeś we właściwości scope dyrektywy. Jeśli użyjesz element.isolateScope () , otrzymasz cały izolowany zakres. Na przykład, jeśli twoja dyrektywa wygląda mniej więcej tak:

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

Następnie wywołanie elementu.scope () w teście powróci

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

Ale jeśli wywołasz element.isolateScope (), otrzymasz

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

Jest to prawdą w przypadku kątowego 1.2.2 lub 1.2.3, nie jestem pewien dokładnie. W poprzednich wersjach miałeś tylko element.scope ().

Yair Tavor
źródło
1
v1.2.3 feat (jqLite): expose isolateScope () getter podobny do scope () github.com/angular/angular.js/commit/ ...
daniellmb
1
ale gdzie szpiegujesz metodę $ watch?
tusharmath
1
możesz ujawnić funkcję, która działa na zegarku $ watch, a następnie ją szpiegować. W dyrektywie ustaw „scope.myfunc = function () ...”, a następnie w $ watch do „$ scope. $ Watch ('myName', scope.myfunc);”. Teraz w teście możesz pobrać myFunc z izolowanego lunety i szpiegować go.
Yair Tavor
22
Nie działa na mnie. element.isolateScope()zwraca undefined. I element.scope()zwraca zakres, który nie zawiera wszystkich elementów, które umieściłem w moim zakresie.
mcv
4
@mcv Okazało się, że muszę to zrobićelement.children().isolateScope()
Will Keeling
11

Możesz zrobić, var isolateScope = myDirectiveElement.scope()aby uzyskać zakres izolowania.

Nie musisz jednak tak naprawdę testować wywołania $ watch ... to bardziej testowanie angularjs niż testowanie aplikacji. Ale myślę, że to tylko przykład na pytanie.

Andrew Joslin
źródło
2
Nie jestem pewien, czy zgadzam się, że to „testowanie kątowe”. Nie testuję, czy $ watch działa, ale po prostu, że dyrektywa jest właściwością „podłączona” do kątowej.
daniellmb
1
Również daniellmb, sposobem na przetestowanie tego byłoby ujawnienie swojej greetfunkcji i szpiegowanie tego, i sprawdzenie, czy to się nazywa - a nie $ watch.
Andrew Joslin,
Tak, to sztuczny przykład, ale interesowało mnie, czy istnieje czysty sposób testowania zakresu izolowanego. Przerwanie enkapsulacji i umieszczenie metod w zakresie nie zadziała w tym przypadku, ponieważ nie ma podpięcia do dodania szpiega przed jego wywołaniem.
daniellmb
@AndyJoslin, Z ciekawości, po isolateScopeco w ogóle tworzyć zmienną? Zobacz komentarz Angula do tego wideo z jajogłowym ( egghead.io/lessons/angularjs-unit-testing-directive-scope ): Począwszy od Angular 1.2, aby pobrać izolowany zakres, należy użyć element.isolateScope()zamiast element.scope() code.angularjs.org/1.2. 0 / docs / api / angular.element
Danger
1

przenieś logikę do osobnego sterownika, tj .:

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

i przetestuj to drugie, tak jak każdy inny kontroler - przekazując obiekt zakresu bezpośrednio (zobacz przykład w sekcji Kontrolery ).

jeśli chcesz uruchomić $ watch w swoim teście, wykonaj:

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
źródło
0

Nie jestem pewien, czy jest to możliwe z celownikiem izolowanym (chociaż mam nadzieję, że ktoś udowodni, że się mylę). Zakres izolowany, który jest tworzony w dyrektywie, jest, cóż, izolowany, więc metoda $ watch w dyrektywie różni się od zakresu, który szpiegujesz w teście jednostkowym. Jeśli zmienisz zakres: {} na zakres: prawda, zakres dyrektywy odziedziczy prototypowo i testy powinny przejść.

Wydaje mi się, że nie jest to najbardziej idealne rozwiązanie, ponieważ czasami (często) izolowany zakres jest dobrą rzeczą.

UnicodeSnowman
źródło