Jasmine - unit testing a directive: Getting 'undefined' is not an object - angularjs

I am taking baby steps in Jasmine, please bear with me for any blatant mistakes..I am writing test cases to check if a controller method - transformData gets called, the details below
My Directive
angular.module('myModule')
.directive('myDirective', [ function ()
{
'use strict';
return {
restrict: 'E',
templateUrl: '/static/quality/scripts/directives/hh-star-rating.html',
scope: {
varA:'#',
},
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
$scope.transformData = function(data)
{
/// does something;
return data;
};
}
],
My Spec
describe('directive - hh-star-ratings', function() {
'use strict';
angular.module('myModule', [])
.directive('myContainer', function() {
return {
restrict: 'E',
priority: 100000,
terminal: true,
template: '<div></div>',
controller: function($scope) {
$scope.loadingData = true;
this.stopLoading = function() {
$scope.loadingData = false;
};
}
}
});
var result_set = {
//some data-transform-req
};
beforeEach(module('myModule'));
var element, scope, controller;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element(
'<my-container> <my-directive> </my-directive> </my-container>'
);
$compile(element)(scope);
scope.$digest();
controller = element.controller('myDirective');
}));
it('should call the transformData', function() {
controller.transformData(result_set);
scope.$apply();
scope.transformData.should.have.been.called;
});
})
Issue: When I run the test, I get the following error
TypeError: 'undefined' is not an object (evaluating 'controller.transformData')
What am I doing wrong? Thanks in advance for your time.

Your define the function transformData in the scope - NOT in the controller
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
this.transformData = function(data) // <= change to this (controller)
{
/// does something;
return data;
};
}
],

Related

Unit test for directive with $interval.flush throw 'Possibly unhandled rejection'

I have created directive that use $interval service:
directive('dir', ['$animate', '$interval', function($animate, $interval) {
return {
restrict: 'E',
scope: {
model:'='
},
controller: function($scope) {
$scope.elements = [];
this.register = function(element) {
$scope.elements.push(element);
}
$scope.animate = function() {
//do some animation
}
},
link: function(scope) {
scope.animation = $interval(function() {
scope.animate();
}, 500);
scope.$watch('model', function(value) {
if(value == false) {
if(scope.animation) {
$interval.cancel(scope.animation);
scope.animation = undefined;
}
}
});
}
};
When I write a unit test for animate method I receive:
Possibly unhandled rejection: {} thrown
beforeEach(inject(['$compile', '$rootScope', '$interval',
function($compile, $rootScope, $interval) {
var element = angular.element('<dir model="true"></dir>');
var directive = $compile(element)($rootScope.$new());
controller = directive.controller('loading');
scope = directive.isolateScope();
interval = $interval;
scope.$digest();
}]));
fit('trigger animation', function() {
interval.flush(500);
});
As I can understand this is because of not resolved Promise that was returned by $interval inside my link function. But can't figure out how to do it properly.
"angular": "~1.6.0",
"angular-mocks": "~1.6.0",

Get instance of directive controller with Angular/Jasmine

When unit testing angular directives, how can I grab an instance of the directive controller and assert certain data bindings on the controller?
function myDirective() {
return {
restrict: 'E',
replace: true,
templateUrl: 'tpl.html',
scope: { },
controller: function($scope) {
$scope.text = 'Some text';
}
};
}
angular
.module('example')
.directive('myDirective', [
myDirective
]);
unit test
describe('<my-directive>', function() {
var element;
var $compile;
var $scope;
beforeEach(module('example'));
beforeEach(function() {
inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
});
});
describe('basic functionality', function() {
beforeEach(function() {
element = $compile('<my-directive></my-directive')($scope);
$scope.$digest();
});
it('should bind the correct text', function() {
//?
});
});
});
element = $compile('<my-directive></my-directive')($scope);
$scope.$digest();
ctrl = element.controller('myDirective');
Call element.controller with $scope like element.controller($scope). Some proof of concept bellow.
angular
.module('example', [])
.directive('myDirective', [
function myDirective() {
return {
restrict: 'E',
replace: true,
//template: ['<div>{{ text }}</div>'].join(''),
scope: {},
controller: function($scope) {
$scope.text = 'Some text';
}
};
}
]);
describe('<my-directive>', function() {
var element;
var $compile;
var $scope;
beforeEach(module('example'));
beforeEach(function() {
inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
});
});
describe('basic functionality', function() {
beforeEach(function() {
element = $compile('<my-directive></my-directive')($scope);
$scope.$digest();
});
it('should bind the correct text', function() {
expect(element.controller($scope).scope().$$childTail.text).toEqual('Some text')
});
});
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>

How to write unit test case for scope and timeout function inside directive

I am using isolate scope in custom directive. I have updated plunker link. http://plnkr.co/edit/NBQqjxW8xvqMgfW9AVek?p=preview
Can someone help me in writing unit test case for script.js file.
script.js
var app = angular.module('app', [])
app.directive('myDirective', function($timeout) {
return {
restrict: 'A',
scope: {
content: '='
},
templateUrl: 'my-directive.html',
link: function(scope, element, attr) {
$timeout(function() {
element = element[0].querySelectorAll('div.outerDiv div.innerDiv3 p.myClass');
var height = element[0].offsetHeight;
if (height > 40) {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
}
})
scope.showMore = function() {
angular.element(element).removeClass('expandable');
scope.isShowMore = false;
};
scope.showLess = function() {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
};
}
}
})
(function() {
'use strict';
describe('Unit testing directive', function() {
var $compile, scope, element, compiledDirective, $rootScope, $timeout;
beforeEach(module("app"));
beforeEach(inject(function(_$compile_, _$rootScope_, _$timeout_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
$timeout = _$timeout_;
element = angular.element(' <div my-directive content="content"></div>');
compiledDirective = $compile(element)(scope);
scope.$digest();
}));
it('should apply template', function() {
expect(compiledDirective.html()).toBe('');
});
it('check for timeout', function() {
$timeout.flush();
});
});
})();
Use $timeout.flush() function for writing testcase for $timeout
it('check for timeout', function() {
scope.digest();
// flush timeout(s) for all code under test.
$timeout.flush();
// this will throw an exception if there are any pending timeouts.
$timeout.verifyNoPendingTasks();
expect(scope.isShowMore).toBeTruthy();
});
Check this article for better understanding.

How to test change on scope executed in directive controller

I have directive myItem. I want to change one property that is passed from parent to directive, so I use controller in myItem where I divide value by 60.
Everything works fine on the website.
Directive
define(['angular',
'core',
'ui-bootstrap',
],
function(angular, coreModule, uiBootStrap) {
'use strict';
function myItemDirective(APPS_URL) {
return {
restrict: 'E',
replace: 'true',
scope: {
item: '='
},
templateUrl: APPS_URL.concat('item.tpl.html'),
controller: ['$scope', function($scope) {
$scope.item.property = Math.round($scope.item.property / 60);
}]
};
}
return angular.module('apps.myItemModule', [coreModule.name])
.directive('myItem', ['APPS_URL', myItemDirective]); });
Now I would like to write a test where I can check that rendered value in directive is value passed from parent divided by 60.
Unit Test
define([
'angular',
'angularMocks',
'apps/directives/mydirective' ], function (angular, mocks, myItemDirective) {
var $httpBackend, $controller, $rootScope, $scope, directive,
item = {
property: 60
};
describe('my directive', function () {
beforeEach(function() {
mocks.module(myItemDirective.name);
mocks.inject(function(_$rootScope_, $injector, $window) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = $injector.get('$compile');
compileDirective();
});
});
afterEach(function() {
$scope.$destroy();
element.remove();
});
function compileDirective() {
element = angular.element('<my-item item=' + JSON.stringify(item) + '></my-item>');
directive = $compile(element)($scope);
$scope.$digest();
directiveScope = element.isolateScope();
}
it('test', function(){
// console.log(element.find('#item-holder').innerText)
// > 60
expect(element.find('#item-holder').innerText).toEqual(Math.round(item.propert/60));
// this will fail because item.property was not divided by 60
});
}); });
Problem
I am not able to render directive in unit test with value divided by 60. I can see in console that controller in directive has been called but the value is not changed.
The problem was related to tests using the same reference to object item.
To fix this:
moved item to beforeEach
changed the way to create element
changed the way to get directive scope
use $scope.$apply()
So test looks like:
define([
'angular',
'angularMocks',
'apps/directives/mydirective' ], function (angular, mocks, myItemDirective) {
var $httpBackend, $controller, $rootScope, $scope, directive,
item;
describe('my directive', function () {
beforeEach(function() {
item = {
property: 60
};
mocks.module(myItemDirective.name);
mocks.inject(function(_$rootScope_, $injector, $window) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = $injector.get('$compile');
compileDirective();
});
});
afterEach(function() {
$scope.$destroy();
element.remove();
});
function compileDirective() {
$scope.item = item;
element = angular.element('<my-item item="item"></my-item>');
directive = $compile(element)($scope);
$scope.$apply();
directiveScope = directive.isolateScope();
}
it('test', function(){
// console.log(element.find('#item-holder').innerText)
// > 60
expect(element.find('#item-holder').innerText).toEqual(Math.round(item.propert/60));
// this will fail because item.property was not divided by 60
});
}); });

Using jasmine to test angular directive that uses a controller

I am trying to write a jasmine test that will test if an angular directive I've written is working.
Here is my spec file:
describe('blurb directive', function () {
var scope, httpMock, element, controller;
beforeEach(module('mdotTamcCouncil'));
beforeEach(module('mdotTamcCouncil.core'));
beforeEach(module('blurb'));
beforeEach(inject(function (_$httpBackend_, $rootScope, $compile) {
element = angular.element('<mcgi-blurb text-key="mainPageIntro"></mcgi-blurb>');
var httpResponse = '<textarea name="content" ng-model="content"></textarea>';
scope = $rootScope.$new();
httpMock = _$httpBackend_;
httpMock.whenGET('components/blurb/blurb.html').respond(httpResponse);
element = $compile(element)(scope);
scope.$digest();
}));
it('should have some content', function () {
expect(scope.content).toBeDefined();
});
});
The value "scope.content" is always undefined and when I look at the scope object it seems to be a generic scope object that doesn't have my custom attributes on it.
Here are the other related files:
blurb-directive.js
(function () {
'use strict';
angular.module('blurb')
.directive('mcgiBlurb', blurb);
function blurb() {
return {
restrict: 'E',
replace: true,
templateUrl: jsGlobals.componentsFolder + '/blurb/blurb.html',
controller: 'BlurbController',
controllerAs: 'blurb',
bindToController: false,
scope: {
textKey: "#"
}
};
};
})();
blurb-controller.js
(function () {
angular.module('blurb')
.controller('BlurbController', ['$scope', 'blurbsFactory', 'userFactory', function ($scope, blurbsFactory, userFactory) {
$scope.content = "";
$scope.blurbs = {};
$scope.currentUser = {};
this.editMode = false;
userFactory().success(function (data) {
$scope.currentUser = data;
});
blurbsFactory().success(function (data) {
$scope.blurbs = data;
$scope.content = $scope.blurbs[$scope.textKey];
});
this.enterEditMode = function () {
this.editMode = true;
};
this.saveEdits = function () {
this.editMode = false;
$scope.blurbs[$scope.textKey] = $scope.content;
};
}]);
})();
What am I doing wrong?
The directive has isolated scope, so the scope passed to its controller and link function (if there was one), is the isolated one, different than your scope.
You may have luck getting the scope of the directive using element.isolateScope(); you may not, because of the replace: true - try to make sure. You may also access the controller instance using element.controller('mcgiBlurb').

Resources