AngularJS: Testing a Directive’s Controller with Isolate Scope Expressions

When writing unit tests for an AngularJS directive, there’s not a breadth of information online past the ‘basics’. This fact came to me rather quickly when I tried to write a simple unit test titled: “it(‘should call the passed function’…”. I had created a directive which accepted an isolate scope expression passed from the parent controller. Side-note: a good recap on isolate scope is found here. I finally figured it out with a little ‘controllerAs’ magic.

To follow along, open this jsfiddle!

“How did this work and what was I testing?”

myApp.controller('MyDirectiveController', ['$scope',function($scope){
self.doSomething = function (){
//Call the passed expression

This is the entire directive’s controller. My function in the directive’s controller (‘doSomething’) calls the passed expression (‘passedExpression’) from the parent controller. What I was trying to do in my test was verify that when a method in the directive’s controller was called, it was actually calling the function on the parent controller.

The Directive

myApp.directive('myDrtv', function () {
return {
restrict: 'E',
scope: {
passedVar: '=',
passedExpression: '&'
template: '
<div>Hello {{passedVar}}</div>
controller: 'MyDirectiveController',
controllerAs: 'myDirectiveCtrl',
replace: false

view raw


hosted with ❤ by GitHub

So, our directive accepts the passed expression and it gets assigned to the directive’s isolate scope as  “$scope.passedExpression”. My initial thought was to simply test the MyDirectiveController without actually compiling the directive, but that doesn’t give us a full test (and would be way too easy to do!).

“Show Us the Test!”

describe('myApp', function () {
var element, scope, innerScope, elementCtrl;
beforeEach(function () {
//Create Element with our directive
element = angular.element('<my-drtv passed-var="passThis" passed-expression="myFunction()">');
inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
//Create scope variables to pass to the directive
scope.passThis = 'Passing';
scope.myFunction = function(){};
//Now our element is ready and behaving like it would on a page
innerScope = element.isolateScope();
elementCtrl = innerScope.myDirectiveCtrl;
it('says hello', function () {
expect(element.text()).toBe('Hello Passing');
it('should call the passed function', function(){
//Watch our main scope's function
//Tell the element to call it's function that calls the parent's function

view raw


hosted with ❤ by GitHub

The first test verifies that our parent scope’s variable was passed to the directive properly. The next one is what caught me. I could not figure out how to call the “doSomething()” function on the element’s controller. I tried using the ‘$controller’ injection to get to it, but the instance it created was not the same as the element’s controller. Once I started debugging, I started looking at the results of the function “element.isolateScope()“. There was a property on that scope object that was returned “myDirectiveCtrl” that I immediately found out was the directive’s controller instance! After that I could call whatever I wanted on it. With jasmine, I’m spying on my parent scope’s function to make sure it is being called.

I did see that there was an “element.controller()” function, but it never worked properly for me. If it works for you, please let me know!

“Now what?”
Go write some better unit tests! Also, write as many directives as you can! I’ve found that moving code out of my templates and into directives has really helped modularize my projects. Being able to reuse a directive is such a time (and code) saver!

Once again, this example lives on jsfiddle.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s