I can think of two approaches:
1) Use both directives
Let’s assume we have the following directives:
app.directive('foo', function() {
return {
restrict: 'E',
controller: function($scope) {
this.add = function(x, y) {
return x + y;
}
}
};
});
app.directive('bar', function() {
return {
restrict: 'E',
require: '^foo',
link: function(scope, element, attrs, foo) {
scope.callFoo = function(x, y) {
scope.sum = foo.add(x, y);
}
}
};
});
In order to test the callFoo method, you can simply compile both directives and let bar use foo‘s implementation:
it('ensures callFoo does whatever it is supposed to', function() {
// Arrange
var element = $compile('<foo><bar></bar></foo>')($scope);
var barScope = element.find('bar').scope();
// Act
barScope.callFoo(1, 2);
// Assert
expect(barScope.sum).toBe(3);
});
Working Plunker.
2) Mock foo’s controller out
This one is not quite straightforward and a little tricky. You could use element.controller() to get the controller of an element, and mock it out with Jasmine:
it('ensures callFoo does whatever it is supposed to', function() {
// Arrange
var element = $compile('<foo><bar></bar></foo>')($scope);
var fooController = element.controller('foo');
var barScope = element.find('bar').scope();
spyOn(fooController, 'add').andReturn(3);
// Act
barScope.callFoo(1, 2);
// Assert
expect(barScope.sum).toBe(3);
expect(fooController.add).toHaveBeenCalledWith(1, 2);
});
Working Plunker.
The tricky part comes up when one directive uses the other’s controller right away in its link function:
app.directive('bar', function() {
return {
restrict: 'E',
require: '^foo',
link: function(scope, element, attrs, foo) {
scope.sum = foo.add(parseInt(attrs.x), parseInt(attrs.y));
}
};
});
In this case you need to compile each directive individually so you can mock the first one out before the second one uses it:
it('ensures callFoo does whatever it is supposed to', function() {
// Arrange
var fooElement = $compile('<foo></foo>')($scope);
var fooController = fooElement.controller('foo');
spyOn(fooController, 'add').andReturn(3);
var barElement = angular.element('<bar x="1" y="2"></bar>')
fooElement.append(barElement);
// Act
barElement = $compile(barElement)($scope);
var barScope = barElement.scope();
// Assert
expect(barScope.sum).toBe(3);
expect(fooController.add).toHaveBeenCalledWith(1, 2);
});
Working Plunker.
The first approach is way easier than the second one, but it relies on the implementation of the first directive, i.e, you’re not unit testing things. On the other hand, although mocking the directive’s controller isn’t so easy, it gives you more control over the test and removes the dependency on the first directive. So, choose wisely. 🙂
Finally, I’m not aware of an easier way to do all of the above. If anyone knows of a better approach, please improve my answer.