Как Unit Test Директива по отдельным областям в AngularJS
Что такое хороший способ для unit test изолированной области в AngularJS
JSFiddle, показывающий unit test
Фрагмент директивы
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));
});
UPDATE:
Я получил его для работы, проверив, были ли ожидаемые наблюдатели добавлены в область дочерних элементов, но он очень хрупкий и, вероятно, с помощью аксессуаров недокументированным способом (который может быть изменен без уведомления!).
//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');
Ответы
Ответ 1
См. angular элемент api docs. Если вы используете 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().
Ответ 2
Вы можете сделать var isolateScope = myDirectiveElement.scope()
, чтобы получить область выделения.
Вам не нужно проверять, что $watch был вызван, хотя.. что больше тестирования angularjs, чем тестирование вашего приложения. Но я думаю, это просто пример для вопроса.
Ответ 3
переместите логику на отдельный контроллер, то есть:
//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
});
});
Ответ 4
Я не уверен, что это возможно с изолированной областью (хотя я надеюсь, что кто-то докажет мне, что я ошибаюсь). Область выделения, которая создается в директиве, изолирована, поэтому метод $watch в директиве отличается от области действия, которую вы просматриваете в unit test. Если вы измените область действия: {} в область: true, область действия наследует прототип и ваши тесты должны пройти.
Я думаю, это не самое идеальное решение, потому что иногда (много времени) изолировать область - это хорошо.