Unit testing directive's controller - angularjs

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.

Related

Testing directive with filter dependency

I would like to test a directive, which has a filter dependency. I would like to inject actual filter, instead of using a mock.
Here is my mocked beforeEach. How do I go about injecting actual filter? I've tried injecting as part of the inject function, but this does not seems to work.
beforeEach(function() {
// filter mock
someFilterMock = function(value) {
return value;
};
// get app
module('app');
// get html templates
module('templates');
// replace filter with a mock
module(function($provide) {
$provide.value('someFilterFilter', someFilterMock);
});
// inject & compile
inject(function($rootScope, $compile) {
// create scope
scope = $rootScope.$new();
// create element using directive
element = angular.element('<this-is-directive />');
$compile(element)(scope);
scope.$digest();
});
});
I am doing something like this in my tests:
it('uses a filter', inject(function ($filter) {
var result = $filter('filterName')(params);
expect(result).toBe('something');
}));
Perhaps it does help?

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.

AngularJS Unit testing a directive that needs the $compile provider

I'm trying to test a directive.
This directive use the $compile provider.
I would try to expect if $compile will been called but providing it in the test I'm getting this error:
TypeError: 'undefined' is not a function (evaluating '$compile(angular.element(html))(scope)')
I know why is that happening (I'm overriding with a fake mock the actual $compile provider) but I don't really know how can I fix that problem.
This is the actual test code:
describe('directive', function () {
var scope, mockCompile;
beforeEach(function () {
mockCompile = jasmine.createSpy();
module('directive', function ($provide) {
$provide.value('$compile', mockCompile);
});
var html = '<div directive="foo"></div>';
// The problem is there. I'm injecting the mocked compile service
// Not the real one
inject(function ($compile, $rootScope) {
scope = $rootScope.$new();
$compile(angular.element(html))(scope);
scope.$digest();
});
});
it("should test the directive", function () {
//Act.
scope.$apply();
//Assert.
expect(mockCompile).toHaveBeenCalled();
});
});
First, make sure you have included angular-mocks.js for testing.
Then, each service you mock needs to begin and end with an underscore;
inject(function ($compile, $rootScope)
Should really by;
inject(function (_$compile_, _$rootScope_)

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/

Testing angularjs directive with dependencies

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);

Resources