In some of my directives, I\'m adding functions to the scope to handle logic specific for the directive. For example:
link: function(scope, element, attrs) {
I resolved this issue a bit differently.
If you have a very simple link function in your directive and you don't happen to need the 3rd argument(attrs), just get rid of the link function and assign the directive a controller instead.
app.directive('loadIndicator', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'blahblah/indicator.html',
controller: 'LoadIndicatorController'
};
});
just as you have the args for scope and element in a directive's link function, these 2 args can be injected into an easy-to-test controller as $scope and $element.
If you are capable of creating controllers and unit testing those controllers, then this will be really easy to do.
As stated in a comment reply to @sqlexception. You just really need to get a handle on the directive's scope, which is not hard to do. What you dont want to do is modify your code to satisfy your tests, as it should be the other way around.
To get the scope of the directive just compile like so:
var element = $compile(<directive's html>).($scope)
where $scope
is declared with $scope = $rootScrope.$new()
. Now we can get the isolate scope by doing element.scope()
I just recently wrote a short blog post about this found here Testing a Linking Function with a plunker to help:
If needed, it's possible to directly unit test the link method of a directive. See the unit tester of the angular-ice module: "testing a directive configuration"
http://bverbist.github.io/angular-ice/#/unitTester
example usage: https://github.com/bverbist/angular-ice/blob/master/app/components/icebank/bank-account-number-directive_link_test.js
In your case you can keep a reference to the scope object you pass to the directive's link method, and then you can directly test the doStuff function on that scope.
it('should get called on a click', inject(function($rootScope, $compile) {
var scope = $rootScope.$new();
element = $compile('<div ng-click="doIt()"></div>')(scope);
scope.$digest();
expect(scope.$$childHead.doIt()).toBeDefined();
}));
Using this $$childHead was the solution for me to the same problem, with this I can cover functions that weren't being called in my tests.
Basically, rather than test the link function itself, you'd test the outcome(s) of the directive programmatically. What you would do is write out the directive to a string, and use $compile
to have angular process it. Then you test the output to make sure everything is wired up correctly.
Angular's source is full of good examples of how to do this... for example Angular's test of the ngRepeat directive
You can see what they're doing is setting up the directive, Changing the scope (in this case $rootScope
) making sure it's $digest
ed, and then testing the DOM it outputs to make sure everything is wired up correctly. You can also test what's in the scope, if the directive is altering that.
The test for ngClick is also pretty interesting, because it shows testing of a browser interaction and it's effect on the scope.
For sake of completeness, here's a snippet from the ngClick tests that I think sums up testing a directive fairly well:
it('should get called on a click', inject(function($rootScope, $compile) {
element = $compile('<div ng-click="clicked = true"></div>')($rootScope);
$rootScope.$digest();
expect($rootScope.clicked).toBeFalsy();
browserTrigger(element, 'click');
expect($rootScope.clicked).toEqual(true);
}));
So in the case of your scope.doStuff
function, I wouldn't test what it's doing, so much as I'd test whatever it's affected on the scope, and it's subsequently effected DOM elements.