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');
Odpowiedzi:
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 ().
źródło
element.isolateScope()
zwracaundefined
. Ielement.scope()
zwraca zakres, który nie zawiera wszystkich elementów, które umieściłem w moim zakresie.element.children().isolateScope()
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.
źródło
greet
funkcji i szpiegowanie tego, i sprawdzenie, czy to się nazywa - a nie $ watch.isolateScope
co 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()
zamiastelement.scope()
code.angularjs.org/1.2. 0 / docs / api / angular.elementprzenieś 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 }); });
źródło
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ą.
źródło