Testing angularjs directive with dependencies - angularjs

I'm new to AngularJs and having problem trying to test a directive with dependency (although the directive itself works as expected). I was unable to find any answers here or on the other resources.
Here is my code:
Directive:
angular.module('MyApp')
.directive('appVersion', ['config', function (config) {
return function (scope, elm) {
elm.text(config.version);
};
}]);
Service (value):
angular.module('MyApp')
.value('config', {
version: '0.1'
});
Test:
describe('Directive: AppVersion', function () {
beforeEach(module('MyApp'));
var element;
it('should have element text set to config value', inject(function ($rootScope, $compile, config) {
var scope = $rootScope;
element = $compile('<app-version></app-version>')(scope);
expect(element.text()).toBe(config.version);
}));
});
My test is failing with message:
Error: Expected '' to be '0.1'.
meaning that config value got injected properly, but $complile was not using it. I would really appreciate any help on this. Thanks.

You didn't specify the restrict attribute of the directive.
When you don't specify it, that means angular looks for app-version declared as an attribute, not an element.
So you can either add the restrict attribute to the directive or change your template :
element = $compile('<div app-version></div>')(scope);

Related

Angular unit testing: Argument 'fn' is not a function, got Object

This error is occurring when I try to instantiate the setup phase of my unit test. I am unit testing a directive, which has its own controller. For best practice purposes I can always add the controllerAs property to the directive to assign the controller a name, but I get the same error if I do that anyway.
describe('myDirective', function() {
beforeEach(module('app'));
beforeEach(module('app/directives/directive.html'));
var theArgs = {
arg1 : [],
arg2 : 'id',
arg3 : [],
arg4 : '',
arg5 : false
};
beforeEach(inject(function($templateCache, _$compile_, _$rootScope_, $controller) {
template = $templateCache.get('app/directives/directive.html');
$templateCache.put('app/directives/directive.html', template);
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
scope.args = theArgs;
ctrl = $controller({
$scope: scope
});
}));
it('should compile', function() {
var myElement = angular.element('<div my-directive args="theArgs" ></div>');
var element = $compile(myElement)(scope);
// Grab scope. Depends on type of scope.
scope = element.isolateScope() || element.scope();
// Grab controller instance
controller = element.controller(ctrl);
$rootScope.$digest();
// Mock the directive's controller's add() function
scope.add();
});
});
The error is occurring within this block:
ctrl = $controller({
$scope: scope
});
Since the controller doesn't have a name, I am not passing it one in the above code block. That shouldn't throw an error by itself though, right? I don't think there is a problem with my karma configuration since my other 500 tests are all passing.
The second error is being thrown at controller = element.controller(ctrl);, where it can't find the ctrl variable. That error makes sense because it's caused by the first error, but I can't figure out how to fix the first error.
UPDATE: Added directive code to show how the controller was defined. It was never assigned a name, it is anonymous, and I didn't use the controllerAs property, because it returns an error.
app.directive('myDirective', function() {
var dirController = ['$scope', function($scope) {
$scope.add = function() { ... };
}];
return {
restrict: 'A',
scope: {
args: '='
},
templateUrl: '/path/to/template.html',
controller: dirController
};
});
Well the problem is exactly with this code section:
ctrl = $controller({
$scope: scope
});
Since $controller is require the name of the controller for the first parameter, and then the injectables afterwards within an object literal.
E.g.: Tell it which controller should it create:
ctrl = $controller('MyControllerName', {
$scope: scope
});

Unit testing directive's controller

I am struggling to get the controller from within a directive for unit testing. Here is my angular app:
angular.module('test-app', [])
.controller('loadingCtr', ['$scope', function ($scope) {
}])
.directive('loading', function() {
return {
restrict: 'E',
controller: 'loadingCtr'
};
});
Here is my unit test code:
describe('loading', function () {
beforeEach(inject(function($rootScope, $compile) {
var fooElement = $compile('<loading></loading>')($rootScope);
var fooController = fooElement.controller('loading');
$rootScope.$digest();
console.log(fooController);
}));
describe('loading: testing loading directive', function() {
it('should create loading directive', function() {
});
});
});
Here is a plnkr to mess around with: http://plnkr.co/edit/38cc5HQFgeHDhnC8OMo8?p=info
fooController always returns as undefined. I've tried using the following examples I've found online, but I always get the same results:
Unit testing a directive that defines a controller in AngularJS
http://daginge.com/technology/2014/03/03/testing-angular-directive-controllers-with-jasmine-and-karma/
Is there something obvious here that I am missing?
Only issue i can see is that you are not loading the module test-app in your fixture, which means that the compiled html code does not really compile the directive loading since it is not available in the injector. So try loading the module in the beforeEach block. Loading the module ensures that directives, controllers, services etc registered under the module is available in the injector otherwise it will just use the module as ng which does not know anything about the loading directive.
i.e
describe('loading', function () {
var fooController;
//Load the module
beforeEach(module('test-app'));
beforeEach(inject(function($rootScope, $compile) {
var fooElement = $compile('<loading></loading>')($rootScope);
fooController = fooElement.controller('loading');
$rootScope.$digest();
console.log(fooController);
}));
describe('loading: testing loading directive', function() {
it('should create loading directive', function() {
expect(fooController).toBeDefined();
});
});
});
Demo
Also note that if you are registering the controller with .controller you can directly get the controller instance by $controller(ctrlName) construct. If you are using controllerAs syntax with bindToController:true in your directive then you can get it from the scope with the property name same as the alias as well.

Testing angularjs directive attributs with jasmine

I am relatively new to jasmine tests, and I've got some problem with it. I try to test this directive :
DIRECTIVE
myApp.LoadingsDirective = function() {
return {
restrict: 'E',
replace: true,
template: '<div class="loading"><img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" width="20" height="20" /></div>',
link: function (scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.show);
},
function(val) {
if (val){
$(element).show();
}
else{
$(element).hide();
}
})
}
}
}
myApp.directive('loading', myApp.LoadingsDirective);
This directive just show a loading icon until the result of a asynchronious request replace it.
I try something like this :
TEST
describe('Testing directives', function() {
var $scope, $compile, element;
beforeEach(function() {
module('myApp');
inject(function($rootScope, _$compile_) {
$scope = $rootScope.$new();
$compile = _$compile_;
});
});
it('ensures directive show the loading when show attribut is true', function() {
// GIVEN
var element = $compile('<div><loading show="true"> </loading></div>')($scope);
var loadingScope = element.find('loading').scope();
// WHEN
loadingScope.$watch();
// THEN
expect(loadingScope.show).toBe('true');
});
});
What is the best way to test this type of directive ? How to get access to attributs and test it ?
I always do it this way (coffeescript, but you'll get the idea):
'use strict';
describe 'Directive: yourDirective', ->
beforeEach module('yourApp')
# angular specific stuff
$rootScope = $compile = $scope = undefined
beforeEach inject (_$rootScope_, _$compile_) ->
$rootScope = _$rootScope_
$scope = $rootScope.$new()
$compile = _$compile_
# object specific stuff
element = createElement = undefined
beforeEach inject () ->
createElement = () ->
element = angular.element("<your-directive></your-directive>")
$compile(element)($scope)
$scope.$digest()
it "should have a headline", ->
createElement()
element.find("a").click()
$scope.$apply()
expect(element.find("input").val()).toEqual("foobar")
expect($scope.inputModel).toEqual("foobar")
And this could be the directive:
<your-directive>
<a ng-click="spanModel='foobar'">set inputModel</a>
<input ng-model="inputModel">
</your-directive>
First, I extract the creation of your element into a function. This allows you to do some initial setup before the directive is created.
Then I perform some actions on my directive. If you want to apply this actions into your scope (remember in jasmine you are NOT inside angulars' digest circle), you have to call $scope.$apply() or $scope.$digest() (can't remember right now what the exact difference was).
In the example above, you click on the <a> element, and this has a ng-click attached. This sets the inputModel scope variable.
Not tested, but you'll get the idea

Testing Angular Directive with Dependencies like $location and $routeParams

Thie question is somewhat related to How do I inject a mock dependency into an angular directive with Jasmine on Karma. But I cant figure it out. Heres the thing:
I have a simple angular directive for rendering a head-part of my apllication with several parameters. One is passed, two came from the URL vie $location and $routeParam. The directive looks like this:
'use strict';
myApp.directive('appHeader', ['$routeParams', '$location', function ($routeParams, $location) {
return {
restrict: 'E',
templateUrl: 'path/to/partials/template.html',
scope: {
icon: '#icon'
},
link: function (scope, element, attributes) {
var lastUrlPart = $location.path().split('/').pop();
scope.project = $routeParams.itemName;
scope.context = lastUrlPart === scope.project ? '' : lastUrlPart;
}
};
}]);
This is called via <app-header icon="bullhorn"></app-header>.
Now I want to add some tests. As for the template rendering I'm done. The following works like expected. The test passes.
describe('appHeader', function () {
var element, scope;
beforeEach(module('myApp'));
beforeEach(module('myAppPartials'));
beforeEach(inject(function ($rootScope, $compile) {
element = angular.element('<app-header icon="foo"></app-header>');
scope = $rootScope;
$compile(element)(scope);
scope.$digest();
}));
it('should contain the glyphicon passed to the directive', function () {
expect(element.find('h1').find('.glyphicon').hasClass('glyphicon-foo')).toBeTruthy();
});
});
Now I want to test that scope.context and scope.project are set accordingly to the dependencies $location and $routeParams, which I want to mock of course. How can I acieve this.
I tried for instance the answer from the question linked above:
beforeEach(module(function ($provide) {
$provide.provider('$routeParams', function () {
this.$get = function () {
return {
itemName: 'foo'
};
};
});
}));
But In my test
it('should set scope.project to itemName from $routeParams', function () {
expect(scope.project).toEqual('foo');
});
scope.project is undefined:
Running "karma:unit:run" (karma) task
Chrome 35.0.1916 (Mac OS X 10.9.3) appHeader should set scope.project to itemName from routeParams FAILED
Expected undefined to equal 'foo'.
Error: Expected undefined to equal 'foo'.
As for the location dependency I tried to setUp a Mock mysel like this:
var LocationMock = function (initialPath) {
var pathStr = initialPath || '/project/bar';
this.path = function (pathArg) {
return pathArg ? pathStr = pathArg : pathStr;
};
};
Then injection $location in the before each and set a spyOn to the calling of path() like this:
spyOn(location, 'path').andCallFake(new LocationMock().path);
But then, scope.context is undefined, too.
it('should set scope.context to last part of URL', function () {
expect(scope.context).toEqual('bar');
});
Can someone please point out what I am doing wrong here?
Provider's mock works fine, but the problem is in scopes. Your directive has isolated scope. Thus this directive's scope is the child of the scope defined in test. Quick but not recomended fix is:
it('should set scope.project to itemName from $routeParams', function () {
expect(scope.$$childHead.project).toEqual('foo'); });
Try to avoid use scope when testing directives. Better approach will be to mock template and check data in it. For your case it will be something like this:
var viewTemplate = '<div>' +
'<div id="project">{{project}}</div>' +
'</div>';
beforeEach(inject(function ($templateCache) {
$templateCache.put('path/to/partials/template.html', viewTemplate);
}));
and test:
it('should set scope.project to itemName from $routeParams', function () {
expect(element.find('#project').text()).toEqual('foo');
});
for the context it will be the same.

Unit testing plugin directives with requirements

I'm using Videogular in an Angular app I'm working on. I wrote a plugin directive for it that listens to an event broadcast from $rootScope, and, if a video is playing, automatically pauses it when the event is broadcast.
omgYesDirectives.directive('vgAutoPause',
['$rootScope',
function($rootScope) {
return {
restrict: 'E',
require: '^videogular',
link: function($scope, $elem, $attr, $API) {
$rootScope.$on('onGameEnable', onGameEnable);
function onGameEnable(event, data)
{
$API.pause();
}
}
}
}
]);
But I'm having trouble figuring out how to unit test it. I can't seem to properly inject Videogular itself into my test. I've tried variations on this:
describe('vgAutoPause', function () {
var scope, compile, elm, videogular;
beforeEach(inject(function ($compile, $rootScope, videogularDirective) {
videogular = videogularDirective;
scope = $rootScope.$new();
compile = $compile;
}));
it('should instantiate as an HTML element', function () {
elm = compile('<videogular><vg-auto-pause></vg-auto-pause></videogular>')(scope);
scope.$digest();
expect(elm.html()).toContain('vg-auto-pause');
});
});
but Karma keeps complaining about it:
Error: [$injector:unpr] Unknown provider: videogularDirectiveProvider <- videogularDirective
Am I doing it wrong? Do you have any thoughts or suggestions on what I ought to be doing instead?
In AngularJS You can't inject a directive, you must create the HTML and then $compile it to start the $digest cycle.
For example, this is a simple videogular testing:
'use strict';
describe('Directive: Videogular', function () {
var element;
var scope;
beforeEach(module('myApp'));
beforeEach(inject(function ($compile, $rootScope) {
scope = $rootScope;
element = angular.element("<div><videogular><video></video></videogular></div>");
$compile(element)($rootScope);
}));
describe("videogular", function() {
it("should have videogular", function() {
scope.$digest();
expect(element.html()).toContain('<video></video>');
});
});
});
Maybe you need to understand first how to test directives, there's a lot of good info out there. You can start with this links:
http://docs.angularjs.org/guide/unit-testing
https://egghead.io/lessons/angularjs-unit-testing-a-directive
http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/

Resources