80

Co to dobry sposób, aby jednostka testowa izolowane zakresie w angularjsJak Jednostka badania samodzielnie dyrektywy zakresie, w angularjs

JSFiddle showing unit test

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

Chcę zapewnić dyrektywy jest słuchanie w przypadku zmian - nie działa ani w zakresie izolowanym:

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: Dostałem go do pracy, sprawdzając, czy oczekiwane obserwatorów zostały dodane do zakresu dziecięcej, ale jest bardzo kruchy i prawdopodobnie używając akcesorów w nieudokumentowany sposób (aka ulec zmianie bez uprzedzenia!).

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

UPDATE 2: Jak już wspomniałem jest to kruchy! Pomysł nadal działa, ale w nowszych wersjach angularjs akcesor zmieniła od scope() do isolateScope():

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

Znalazłeś sposób na ustawienie szpiegostwo? – Tushar

+0

@Tushar nie jest tak naprawdę, jak wcześniej, jest sposób, aby go uruchomić, ale może ulec zmianie bez powiadomienia, więc używaj na własne ryzyko. – daniellmb

Odpowiedz

99

Zobacz angular element api docs. Jeśli użyjesz element.scope() otrzymasz zakres elementu, który zdefiniowałeś w właściwości scope twojej dyrektywy. Jeśli użyjesz element.isolateScope(), uzyskasz cały wyizolowany zakres. Na przykład, jeśli dyrektywa wygląda mniej więcej tak:

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

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

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

ale jeśli zadzwonisz element.isolateScope() będziesz get

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

Jest to prawda z kanciastych 1.2.2 lub 1.2.3, nie jestem pewien dokładnie. W poprzednich wersjach miałeś tylko element.scope().

+1

v1.2.3 feat (jqLite): pokaż getoster izolScope() podobnego do zakresu () https://github.com/angular/angular.js/commit/27e9340b3c25b512e45213b39811098d07e12e3b – daniellmb

+1

, ale gdzie możesz śledzić metodę $ watch? – Tushar

+1

można ujawnić funkcję, która działa na zegarek $, a następnie szpiegować go. W dyrektywie ustaw "scope.myfunc = function() ...", a następnie w $ watch wykonaj "$ scope. $ Watch (" myName ", scope.myfunc);". Teraz w teście możesz pobrać myFunc z izolowanego zakresu i szpiegować go. –

0

nie jestem pewien, że to możliwe, z zakresu izolowania (choć mam nadzieję, że ktoś udowodni mi źle). Zakres izolatu, który jest tworzony w dyrektywie, jest dobrze izolowany, więc metoda $ watch w dyrektywie różni się od zakresu, w którym szpiegujesz w teście jednostki. Jeśli zmienisz zakres: {} do zakresu: true, zakres dyrektywy dziedziczy prototypowo, a testy powinny przejść.

Domyślam się, że to nie jest idealne rozwiązanie, ponieważ czasami (przez długi czas) izolowanie zakresu jest dobrą rzeczą.

11

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

Naprawdę nie trzeba testować, że $ watch został wywołany, ale jest to bardziej testowanie angularjs niż testowanie aplikacji. Ale myślę, że to tylko przykład na to pytanie.

+2

Nie jestem pewien, czy zgadzam się, że to "testowanie kątowe" Nie testuję tego, że działa $ watch, ale po prostu, że dyrektywa jest właściwością "przewodową" do kątowej. – daniellmb

+1

Również daniellmb, sposobem na sprawdzenie tego byłoby ujawnienie funkcji 'pozdrowić' i szpiegowanie tego i sprawdzenie, czy to się nazywa - a nie zegarek $. –

+0

W porządku, to jest wymyślony przykład, ale byłem zainteresowany, czy istnieje czysty sposób przetestowania zakresu izolacji. Łamanie enkapsulacji i umieszczania metod na zasięgu nie działałoby w tym przypadku, ponieważ nie ma żadnego haka, aby dodać szpiega, zanim zostanie wywołany. – daniellmb

1

ruch logika do oddzielnego kontrolera, czyli:

//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 testy ostatni tak jak każdy kontroler - przekazanie obiektu w zakres bezpośrednio (patrz Kontrolerysection here na przykład).

jeśli trzeba wywołać $ zegarek w teście zrobić:

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 
    }); 
}); 
Powiązane problemy