AngularM AngularM - 1 month ago 7
AngularJS Question

In angular how can I test a scope function is being called in my directive?

In angular how can I test a scope function is being called in my directive?

I'm using jasmine, karma runner and js unit tests with Angular version 1.

This is my directive code:

angular.module("myApp.directives")
.directive("test", ["$rootScope", function($rootScope) {
return {
restrict: "E",
scope: {
"figures": "="
},
templateUrl: "templates/components/test.html",
link: function(scope) {

scope.init = function() {
if (scope.figures !== undefined) {
for (let i = 0; i < scope.figures.length; i++) {
scope.figures[i].isModalVisible = false;
}
}
};

scope.init ();
}
};
}]);


This is my unit test:

describe("test", function () {
var element,
scope,
otherScope
mockData = [
{
isModalVisible: true
},
{
isModalVisible: false
}
];

beforeEach(angular.mock.module("myApp.directives"));

beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.agg = mockData;

element = "<test figures=\"agg\"></test>";
element = $compile(element)(scope);

angular.element(document.body).append(element);

scope.$digest();

otherScope = element.isolateScope();

spyOn(otherScope, "init");
}));

//This one doesn't work
it("should close all modals by default", function () {
expect(otherScope.init).toHaveBeenCalled();
});
});


This is the error I currently get: " Expected spy init to have been called."

Answer

First: your spyOn(scope, "init") and expect(scope, "init") are both performed on the wrong scope. You add init inside directive to isolated scope, so it should be spyOn(otherScope, "init") and expect(otherScope, "init").

Second, the first point will still fail. :) The reason is that init() will be performed only once when link() is called, which will happen on scope.$digest() inside your beforeEach block - but you don't provide data in there. You provide it later in the test when it's too late because directive has already been linked on the first digest cycle and init() would not be called on the second scope.$digect() in your test. To resolve that you need to postpone first digest cycle until after you get all your data ready in the test case itself.

And third, you need first to append html element to the DOM and only then compile it. In such a simple case it will probably work, but if anything inside your element requires directives somewhere on parent elements (i.e. directive inside has a configuration like require: "^something") then compile will fail: there's just no parent elements containing required entities for your new element until you actually insert it into the DOM.

UPDATE

You cannot test this scenario like that. Proper point in time for installing spy would be after adding init() to the scope but before calling it, but the problem is that this point in time is located inside the directive since function call fires immediately after adding function to scope. Since this point is not in the code of test you have no chance to write test in this way.

But let's consider this situation from another point of view. What does init() do? It resets flags on your data array. So, you can initialize those flags with true values before $digest(), and you can check whether those flags were reset or not after calling $digest(). This way you can easily prepare your data and assert results. Your test will be able to clearly tell you if init() was actually called, and even more than that - if it did what it was supposed to do.

Comments