如何在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,因为有时(很多时候),隔离范围是一件好事。