Каков хороший способ модульного тестирования изолированной области в AngularJS
JSFiddle, показывающий модульный тест
Фрагмент директивы
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);
});
}
Я хочу убедиться, что директива отслеживает изменения - это не работает с изолированной областью:
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));
});
ОБНОВЛЕНИЕ: я заставил его работать, проверив, были ли добавлены ожидаемые наблюдатели в дочернюю область, но это очень хрупко и, вероятно, использует аксессоры недокументированным способом (то есть может быть изменено без уведомления!).
//this is super brittle, is there a better way!?
elm = compile(validHTML)(scope);
expect(elm.scope().$$watchers[0].exp).toBe('name');
ОБНОВЛЕНИЕ 2:
Как я уже упоминал, это хрупкое! Идея все еще работает, но в новых версиях AngularJS аксессуар изменился с scope()
на isolateScope()
:
//this is STILL super brittle, is there a better way!?
elm = compile(validHTML)(scope);
expect(elm.isolateScope().$$watchers[0].exp).toBe('name');
Ответы:
См. Документацию по api angular . Если вы используете element.scope (), вы получаете область видимости элемента, которую вы определили в свойстве scope вашей директивы. Если вы используете element.isolateScope (), вы получаете всю изолированную область видимости. Например, если ваша директива выглядит примерно так:
scope : { myScopeThingy : '=' }, controller : function($scope){ $scope.myIsolatedThingy = 'some value'; }
Затем вызов element.scope () в вашем тесте вернет
{ myScopeThingy : 'whatever value this is bound to' }
Но если вы вызовете element.isolateScope (), вы получите
{ myScopeThingy : 'whatever value this is bound to', myIsolatedThingy : 'some value' }
Это верно для angular 1.2.2 или 1.2.3, не уверен точно. В предыдущих версиях у вас был только element.scope ().
источник
element.isolateScope()
возвращаетсяundefined
. Иelement.scope()
возвращает область видимости, которая не содержит всего того, что я поместил в свою область видимости.element.children().isolateScope()
Ты можешь сделать
var isolateScope = myDirectiveElement.scope()
чтобы изолировать прицел.На самом деле вам не нужно проверять, что вызывается $ watch ... это больше тестирование angularjs, чем тестирование вашего приложения. Но я думаю, это просто пример вопроса.
источник
greet
функцию и шпионить за ней, и проверить, вызывается ли она, а не $ watch.isolateScope
вообще создавать переменную? См. Комментарий Энга к этому видео о яйцеголовых ( egghead.io/lessons/angularjs-unit-testing-directive-scope ): Начиная с Angular 1.2, для получения изолированной области необходимо использоватьelement.isolateScope()
вместоelement.scope()
code.angularjs.org/1.2. 0 / docs / api / angular.elementпереместите логику в отдельный контроллер, т.е.
//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);
и протестируйте последний, как и любой другой контроллер - напрямую передавая объект области (см. раздел Контроллеры здесь например, ).
если вам нужно запустить $ watch в вашем тесте, сделайте:
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 }); });
источник
Я не уверен, что это возможно с изолированным прицелом (хотя я надеюсь, что кто-то докажет, что я ошибаюсь). Изолированная область видимости, которая создается в директиве, в общем, изолирована, поэтому метод $ watch в директиве отличается от области, за которой вы наблюдаете в модульном тесте. Если вы измените scope: {} на scope: true, область действия директивы будет унаследована прототипом, и ваши тесты должны пройти.
Я думаю, это не самое идеальное решение, потому что иногда (в большинстве случаев) изолировать область видимости - это хорошо.
источник