Quel est le bon moyen de tester à l'unité le périmètre isolé dans AngularJS?
JSFiddle montrant le test unitaire
Extrait de directive
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);
});
}
Je veux m'assurer que la directive est à l'écoute des modifications - cela ( ne fonctionne pas avec une portée isolée:
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: Je l'ai fait fonctionner en vérifiant si les observateurs attendus ont été ajoutés à la portée des enfants, mais ils sont très fragiles et utilisent probablement les accesseurs dans un document non documenté. manière (aka sujet à changer sans préavis!).
//this is super brittle, is there a better way!?
Elm = compile(validHTML)(scope);
expect(Elm.scope().$$watchers[0].exp).toBe('name');
UPDATE 2: Comme je l’ai dit, c’est cassant! L'idée fonctionne toujours, mais dans les versions plus récentes d'AngularJS, l'accesseur a changé de scope()
à isolateScope()
:
//this is STILL super brittle, is there a better way!?
Elm = compile(validHTML)(scope);
expect(Elm.isolateScope().$$watchers[0].exp).toBe('name');
Voir éléments angulaires api docs . Si vous utilisez element.scope () , vous obtenez la portée de l'élément que vous avez définie dans la propriété scope de votre directive. Si vous utilisez element.isolateScope () , vous obtenez la totalité de la portée isolée. Par exemple, si votre directive ressemble à quelque chose comme ceci:
scope : {
myScopeThingy : '='
},
controller : function($scope){
$scope.myIsolatedThingy = 'some value';
}
Ensuite, appeler element.scope () dans votre test renverra
{ myScopeThingy : 'whatever value this is bound to' }
Mais si vous appelez element.isolateScope (), vous aurez
{
myScopeThingy : 'whatever value this is bound to',
myIsolatedThingy : 'some value'
}
Ceci est vrai à partir de angular 1.2.2 ou 1.2.3, pas vraiment sûr. Dans les versions précédentes, vous n'aviez que element.scope ().
Vous pouvez utiliser var isolateScope = myDirectiveElement.scope()
pour obtenir la portée d'isolement.
Vous n'avez pas vraiment besoin de tester que $ watch s'appelle bien .. c'est plus de tester des angularjs que de tester votre application. Mais je suppose que ce n'est qu'un exemple de la question.
déplacez la logique vers un contrôleur séparé, c'est-à-dire:
//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);
et testez ce dernier comme n'importe quel contrôleur - en passant directement l'objet de la portée (voir Contrôleurs section ici pour un exemple).
si vous avez besoin de déclencher $ watch dans votre test, faites:
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
});
});
Je ne suis pas sûr que ce soit possible avec une portée isolée (bien que j'espère que quelqu'un me prouve le contraire). La portée d'isolement créée dans la directive est, ainsi, isolée. Par conséquent, la méthode $ watch de la directive est différente de la portée que vous espionnez dans le test unitaire. Si vous modifiez la portée: {} en portée: true, la portée de la directive héritera du prototype et vos tests devront réussir.
Je suppose que ce n’est pas la solution idéale, car parfois (souvent), isoler la portée est une bonne chose.