How Unit Test Directive for Specific Areas in AngularJS

What is a good way to unit test an isolated area in AngularJS

JSFiddle showing unit test

Fragment of the 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); }); } 

I want the directive to listen to changes - this does not work with an isolated area:

  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: I got it for work, checking to see if the expected observers were added to the children area, but it is very fragile and probably using accessories in an undocumented way (which can be changed without notice!).

 //this is super brittle, is there a better way!? elm = compile(validHTML)(scope); expect(elm.scope().$$watchers[0].exp).toBe('name'); 

UPDATE 2: As I said, it's fragile! The idea still works, but in newer versions of AngularJS, the accessor has changed from scope() to isolateScope() :

 //this is STILL super brittle, is there a better way!? elm = compile(validHTML)(scope); expect(elm.isolateScope().$$watchers[0].exp).toBe('name'); 
+80
javascript angularjs unit-testing angularjs-directive jasmine
Jun 28 '13 at 19:01
source share
4 answers

See the angular api docs element . If you use element.scope () , you get the scope of the element defined by you in the scope property of your directive. If you use element.isolateScope () , you get the entire isolated area. For example, if your directive looks something like this:

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

Then calling the element.scope () method in your test will return

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

But if you call element.isolateScope (), you will get

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

This is true for angular 1.2.2 or 1.2.3, not entirely accurate. In previous versions, you only had element.scope ().

+99
Dec 01 '13 at 13:32
source share

You can do var isolateScope = myDirectiveElement.scope() to get the selection area.

You do not need to verify that $ watch has been called, though .. which is more angularjs testing than testing your application. But I think this is just an example for the question.

+11
Jun 28 '13 at 22:16
source share

move the logic to a separate controller, i.e.:

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

and test the latter, like any controller, directly pass the scope object (see Controllers for an example).

if you need to run $ watch in your test:

 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 }); }); 
+1
Jan 29 '14 at 23:27
source share

I am not sure if this is possible with an isolated area (although I hope someone will prove to me that I am wrong). The selection area that is created in the directive is isolated, so the $ watch method in the directive is different from the scope that you are viewing in the unit test. If you change the scope: {} to scope: true, the scope inherits the prototype and your tests must pass.

I think this is not the most ideal solution, because sometimes (a lot of time) isolating the area is good.

0
Jun 28 '13 at 20:57
source share



All Articles