Unit Testing $routeParams in directive - angularjs

I have a directive that accesses the $routeParams of the page as such:
myApp.directive("myList", function ($routeParams) {
return {
restrict: 'E',
templateUrl: 'tabs/my-list.html',
link: function (scope) {
scope.year = $routeParams.year;
}
};
});
The directive works as expected and correctly accesses the $routeParams
I am trying to test using angular-mock/jasmine. I can't figure out how to pass mock $routeParams to the directive. This is what I have:
describe('myList', function () {
var scope, compile, element, compiledDirective;
var mockParams = { 'year': 1996 };
beforeEach(function () {
module('templates', 'MyApp');
inject(function ($compile, $rootScope, $routeParams) {
compile = $compile;
scope = $rootScope.$new();
});
element = angular.element('<my-list></my-list>');
compiledDirective = compile(element)(scope);
scope.$digest();
});
it('should fill in the year', function () {
expect(scope.year).toEqual(mockParams.year);
});
});
Which obviously doesn't work because I never passed passed mockParams to the directive. Is there a way to do this?

Mock the $routeParams object mockParams using angular.extend OR do assign mockParams object directly to $routeParams. In that way $routeParams will be available before directive gets compiled.
inject(function ($compile, $rootScope, $routeParams) {
compile = $compile;
scope = $rootScope.$new();
angular.extend($routeParams, mockParams);
});

Related

scope keeps being undefined in angularjs unit-testing

I am trying to unit-testing for following code, I wrote following code for unit-testing like below, I have tried so many ways to work, but I keep getting error:
'Cannot read property 'num' of undefined'
I do not know why scope is not properly set. If you have any idea about it, can you please give some advices?
var angular = require('angular');
require('angular-mocks');
describe('test directive', function () {
let $rootScope;
let $compile;
let scope;
let newScope;
let element;
beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
describe('test directive', function () {
beforeEach(function () {
newScope = $rootScope.$new();
element = $compile('<test-directive></test-directive>')(newScope);
newScope.$digest();
scope = element.isolateScope();
});
fit('scope initialized', function () {
expect(scope.num).toEqual(1);
});
});
});
module.exports = module.directive('testDirective', ['$rootScope', '$scope', function($rootScope, $scope) {
return {
template: require('./test.html'),
restrict: 'E',
controller: [
'$scope',
function ($scope) {
$scope.num = 1
$scope.sum = function(a, b) {
return a + b;
}
}]
}
}]);
The scope variable is undefined because the directive being tested does not have an isolate scope. Instead, use the scope of the element:
beforeEach(function () {
newScope = $rootScope.$new();
element = $compile('<test-directive></test-directive>')(newScope);
newScope.$digest();
̶s̶c̶o̶p̶e̶ ̶=̶ ̶e̶l̶e̶m̶e̶n̶t̶.̶i̶s̶o̶l̶a̶t̶e̶S̶c̶o̶p̶e̶(̶)̶;̶
scope = element.scope();
});
fit('scope initialized', function () {
expect(scope.num).toEqual(1);
});
Be aware that directive has a fatal flaw. It can only use it once within a given scope.

How to write test-case for Directive with in Directive using jasmine

I wrote a test for my directive using jasmine testcase framework with karma testcase runner.
In my project ,I am already having one directive called
<parent-directive></parent-directive>
and i tried to include that parent directive into another one called
<child-directive></child-directive>.
Parent directive elements are converted as components called SampleComponents and included in the child directive
Sample.js
'use strict'
angular.module('Sample')
.directive('SampleHeader', SampleHeader)
function SampleHeader () {
return {
restrict: 'A',
templateUrl: 'header/header.html',
scope: {},
controller: function ($scope) {
$scope.logoutHeader = function () {
console.log('Logout call back')
require('electron').remote.app.quit()
}
}
}
}
SampleSpec.js
describe('SampleHeader', function () {
var $compile, $rootScope, elements, scope, controller
beforeEach(module('Sample'))
beforeEach(module('SampleComponenets'))
beforeEach(module('ngAnimate'))
beforeEach(module('ngRoute'))
beforeEach(module('ngMaterial'))
beforeEach(module('ngCookies'))
beforeEach(module('datatables'))
beforeEach(inject(function (_$compile_, _$rootScope_, _$q_,_$controller_) {
deferred = _$q_.defer()
$compile = _$compile_
$rootScope = _$rootScope_
controller = _$controller_
scope = $rootScope.$new()
elements = angular.element('<sample-header></sample-header>')
$compile(elements)($rootScope.$new())
$rootScope.$digest()
controller = elements.controller('SampleHeader')
scope = elements.isolateScope() || elements.scope()
scope.$digest()
}))
it('should check logoutHeader is called', function () {scope.logoutHeader()
})
})
restrict: 'A' -> it seems you created a attribute directive so you should compile the directive as a attribute (like, elements = angular.element('<div sample-header></div>')).
describe('SampleHeader', function () {
var $compile, $rootScope, elements, scope, controller
beforeEach(module('Sample'))
beforeEach(module('SampleComponenets'))
beforeEach(module('ngAnimate'))
beforeEach(module('ngRoute'))
beforeEach(module('ngMaterial'))
beforeEach(module('ngCookies'))
beforeEach(module('datatables'))
beforeEach(inject(function (_$compile_, _$rootScope_, _$q_,_$controller_) {
deferred = _$q_.defer()
$compile = _$compile_
$rootScope = _$rootScope_
controller = _$controller_
scope = $rootScope.$new()
elements = angular.element('<div sample-header></div>')
$compile(elements)($rootScope.$new())
$rootScope.$digest()
controller = elements.controller('SampleHeader')
scope = elements.isolateScope() || elements.scope()
scope.$digest()
}))
it('should check logoutHeader is called', function () {scope.logoutHeader()
})
})

how to get access to controller scope when testing directive with isolate scope

I have directive with isolate scope and controller, Is it right to write unit tests for directive and test some functional in controller scope?
And if it right, how i can get access to controller scope?
angular.module('myApp').directive('myDirecive', function () {
return {
templateUrl: 'template.html',
scope: {
data: '='
},
controller: function ($scope) {
$scope.f= function () {
$scope.value = true;
};
},
link: function ($scope) {
// some logic
});
}
};
})
describe('myDirective', function () {
'use strict';
var element,
$compile,
$rootScope,
$scope;
beforeEach(module('myApp'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
element = compileElement();
}));
function compileElement() {
var element = angular.element('<my-directive></my-directive>');
var compiledElement = $compile(element)($scope);
$scope.$digest();
return compiledElement;
}
it('should execute f', function () {
$scope.f();
expect($scope.val).toBe(true); // there we can access to isolate scope from directive;
});
});
Controller is given the isolated scope created by the directive. Your $scope, that you pass into compile function, is used as a parent for the directive's isolate scope.
Answering how to test it, you have two options:
Access the isolated scope from the element:
var isolated = element.isolateScope();
For that, you need to have $compileProvider.debugInfoEnabled(true);
Access the isolated scope from your scope:
var isolated = $scope.childHead;
Try this
describe('myDirective', function () {
'use strict';
var element,
dirCtrlScp,
$compile,
$rootScope,
$scope;
beforeEach(module('myApp'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
element = compileElement();
}));
function compileElement() {
var element = angular.element('<my-directive></my-directive>');
var compiledElement = $compile(element)($scope);
dirCtrlScp = element.controller;
$scope.$digest();
return compiledElement;
}
it('should execute f', function () {
dirCtrlScp.f();
expect(dirCtrlScp.value).toBe(true); // there we can access to isolate scope from directive;
});
});
Have a look at this Article

Testing angular js directive's inline controller method using Jasmine?

I facing difficulty in covering the inline controller function defined in a directive using Jasmine and Karma. Below is the code of that directive.
myApp.directive("list", function() {
return {
restrict: "E",
scope: {
items: "=",
click: "&onClick"
},
templateUrl: "directives/list.html",
controller: function($scope, $routeParams) {
$scope.selectItem = function(item) {
$scope.click({
item: item
});
}
}
}
});
Unit Test case is below:
describe('Directive:List', function () {
var element, $scope, template, controller;
beforeEach(inject(function ($rootScope, $injector, $compile, $templateCache, $routeParams) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when("GET", "directives/list.html").respond({});
$scope = $rootScope.$new();
element = $compile('<list></list>')($scope);
template = $templateCache.get('directives/list.html');
$scope.$digest();
controller = element.controller($scope, $routeParams);
}));
it('Test Case-1: should contain the template', function () {
expect(element.html()).toMatch(template);
});
});
In my code coverage report, the controller function is not covered. Also I am not able to get the controller instance and test the selectItem method.
Any idea would be of great help!

isolateScope() returns undefined when using templateUrl

I have a directive that I want to unittest, but I'm running into the issue that I can't access my isolated scope. Here's the directive:
<my-directive></my-directive>
And the code behind it:
angular.module('demoApp.directives').directive('myDirective', function($log) {
return {
restrict: 'E',
templateUrl: 'views/directives/my-directive.html',
scope: {},
link: function($scope, iElement, iAttrs) {
$scope.save = function() {
$log.log('Save data');
};
}
};
});
And here's my unittest:
describe('Directive: myDirective', function() {
var $compile, $scope, $log;
beforeEach(function() {
// Load template using a Karma preprocessor (http://tylerhenkel.com/how-to-test-directives-that-use-templateurl/)
module('views/directives/my-directive.html');
module('demoApp.directives');
inject(function(_$compile_, _$rootScope_, _$log_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
$log = _$log_;
spyOn($log, 'log');
});
});
it('should work', function() {
var el = $compile('<my-directive></my-directive>')($scope);
console.log('Isolated scope:', el.isolateScope());
el.isolateScope().save();
expect($log.log).toHaveBeenCalled();
});
});
But when I print out the isolated scope, it results in undefined. What really confuses me though, if instead of the templateUrl I simply use template in my directive, then everything works: isolateScope() has a completely scope object as its return value and everything is great. Yet, somehow, when using templateUrl it breaks. Is this a bug in ng-mocks or in the Karma preprocessor?
Thanks in advance.
I had the same problem. It seems that when calling $compile(element)($scope) in conjunction with using a templateUrl, the digest cycle isn't automatically started. So, you need to set it off manually:
it('should work', function() {
var el = $compile('<my-directive></my-directive>')($scope);
$scope.$digest(); // Ensure changes are propagated
console.log('Isolated scope:', el.isolateScope());
el.isolateScope().save();
expect($log.log).toHaveBeenCalled();
});
I'm not sure why the $compile function doesn't do this for you, but it must be some idiosyncracy with the way that templateUrl works, as you don't need to make the call to $scope.$digest() if you use an inline template.
With Angularjs 1.3, if you disable debugInfoEnabled in the app config:
$compileProvider.debugInfoEnabled(false);
isolateScope() returns undefined also!
I had to mock and flush the $httpBackend before isolateScope() became defined. Note that $scope.$digest() made no difference.
Directive:
app.directive('deliverableList', function () {
return {
templateUrl: 'app/directives/deliverable-list-directive.tpl.html',
controller: 'deliverableListDirectiveController',
restrict = 'E',
scope = {
deliverables: '=',
label: '#'
}
}
})
test:
it('should be defined', inject(function ($rootScope, $compile, $httpBackend) {
var scope = $rootScope.$new();
$httpBackend.expectGET('app/directives/deliverable-list-directive.tpl.html').respond();
var $element = $compile('<deliverable-list label="test" deliverables="[{id: 123}]"></deliverable-list>')(scope);
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
expect($element).toBeDefined();
expect($element.controller).toBeDefined();
scope = $element.isolateScope();
expect(scope).toBeDefined();
expect(scope.label).toEqual('test');
expect(scope.deliverables instanceof Array).toEqual(true);
expect(scope.deliverables.length).toEqual(1);
expect(scope.deliverables[0]).toEqual({id: 123});
}));
I'm using Angular 1.3.
You could configure karma-ng-html2js-preprocessor plugin. It will convert the HTML templates into a javascript string and put it into Angular's $templateCache service.
After set a moduleName in the configuration you can declare the module in your tests and then all your production templates will available without need to mock them with $httpBackend everywhere.
beforeEach(module('partials'));
You can find how to setup the plugin here: http://untangled.io/how-to-unit-test-a-directive-with-templateurl/
In my case, I kept running into this in cases where I was trying to isolate a scope on a directive with no isolate scope property.
function testDirective() {
return {
restrict:'EA',
template:'<span>{{ message }}</span>'
scope:{} // <-- Removing this made an obvious difference
};
}
function testWithoutIsolateScopeDirective() {
return {
restrict:'EA',
template:'<span>{{ message }}</span>'
};
}
describe('tests pass', function(){
var compiledElement, isolatedScope, $scope;
beforeEach(module('test'));
beforeEach(inject(function ($compile, $rootScope){
$scope = $rootScope.$new();
compiledElement = $compile(angular.element('<div test-directive></div>'))($scope);
isolatedScope = compiledElement.isolateScope();
}));
it('element should compile', function () {
expect(compiledElement).toBeDefined();
});
it('scope should isolate', function () {
expect(isolatedScope).toBeDefined();
});
});
describe('last test fails', function(){
var compiledElement, isolatedScope, $scope;
beforeEach(module('test'));
beforeEach(inject(function ($compile, $rootScope){
$scope = $rootScope.$new();
compiledElement = $compile(angular.element('<div test-without-isolate-scope-directive></div>'))($scope);
isolatedScope = compiledElement.isolateScope();
}));
it('element should compile', function () {
expect(compiledElement).toBeDefined();
});
it('scope should isolate', function () {
expect(isolatedScope).toBeDefined();
});
});

Resources