如何在AngularJS中unit testing隔离的范围指令

在AngularJS中unit testing隔离范围的好方法是什么?

JSFiddle显示unit testing

指令片断

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'); 

请参阅angular元素api文档 。 如果你使用element.scope(),你得到你在指令的scope属性中定义的元素的作用域。 如果你使用element.isolateScope(),你会得到整个隔离的范围。 例如,如果你的指令看起来像这样:

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

然后在你的testing中调用element.scope()将返回

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

但是,如果你调用element.isolateScope(),你会得到

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

对于angular1.2.2或1.2.3来说这是正确的,不能确定。 在以前的版本中,只有element.scope()。

你可以做var isolateScope = myDirectiveElement.scope()来获得隔离作用域。

你真的不需要testing那个$ watch被调用,虽然..这比testing你的应用程序更多的testingangularjs。 但我想这只是一个问题的例子。

将逻辑移至单独的控制器,即:

 //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); 

并像任何控制器那样testing后者 – 直接传递scope对象(参见这里的 控制器 部分 )。

如果您需要在您的testing中触发$ 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 }); }); 

我不确定这是可能的隔离范围(虽然我希望有人certificate我错了)。 在指令中创build的隔离范围很好地隔离了,所以指令中的$ watch方法与您在unit testing中执行的范围不同。 如果将范围({})更改为scope:true,则指令范围将inheritance原型,并且您的testing应通过。

我想这不是最理想的解决scheme,因为有时(很多时候),隔离范围是一件好事。