scope method in AngularJS directive is not a function, when unit testing - javascript

I have this Mocha test:
'use strict';
///////////////////////////////////
describe('all admin page directives', function () {
let scope, $compile, element, tmp;
beforeEach(module('app'));
beforeEach(module('templates'));
afterEach(function () {
scope.$destroy();
});
describe('category', function () {
beforeEach(inject(function ($injector) {
$compile = $injector.get('$compile');
var $rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
scope.rightSidebarData = {};
$compile('<create-category right-sidebar-data="rightSidebarData"></create-category>')(scope);
return scope.$digest();
}));
it('should do something', function () {
scope.updateModel(); // <<<<<< ERROR HERE
});
});
});
here is my directive:
/* globals angular */
angular.module('app').directive('createCategory',
['UserService', 'AssignmentService', 'NotificationService', 'USER', 'UserInfoService', 'AlertService', '$window',
function (UserService, AssignmentService, NotificationService, USER, UserInfoService, AlertService, $window) {
return {
scope: {
rightSidebarData: '=',
},
restrict: 'EA',
templateUrl: "pages/admin/views/templates/category/create-category.html",
link: function ($scope, el, attrs) {
$scope.rightSidebarData.setOrReset = function () {
$scope.setOrReset();
};
},
controller: function ($scope, FUNCTIONAL_TEAM_ENUM, CATEGORY_ENUM, CategoryService) {
const rsd = $scope.rsd = $scope.rightSidebarData;
$scope.setOrReset = function () {...};
$scope.updateModel = function () {...};
$scope.saveModel = function () {...};
},
};
}
]);
I am getting this error:
TypeError: scope.updateModel is not a function
Does anyone know what I need to do in my test to fix this?
Also, how do I know if I need to use $rootScope.$new() or if I should be passing the parent controller's scope?

I had to add another preprocessor in my karma config file:
preprocessors: {
'./public/pages/**/views/**/*.html':['ng-html2js'], // this
},
and then using the following, it worked:
'use strict';
describe('all admin page directives', function () {
let scope, el, tmp, isoScope;
beforeEach(module('app'));
beforeEach(module('ngMockE2E'));
beforeEach(module('templates'));
afterEach(function () {
scope.$destroy();
});
describe('category', function () {
beforeEach(inject(function ($injector) {
const $compile = $injector.get('$compile');
const $rootScope = $injector.get('$rootScope');
const $templateCache = $injector.get('$templateCache');
console.log('create category template =>', $templateCache.get('pages/admin/views/templates/category/create-category.html'));
scope = $rootScope.$new();
scope.rightSidebarData = {};
scope.rightSidebarData.model = {};
let element = angular.element(`<create-category right-sidebar-data="rightSidebarData"></create-category>`);
el = $compile(element)(scope);
$rootScope.$digest();
scope.$digest();
el.isolateScope().setOrReset();
}));
it('should do something', function () {
el.isolateScope().updateModel();
});
});
});

Related

Unit-testing $timeout execution inside an async call

I'm trying to unit-test a piece of my code contained inside an async call, like this:
function init() {
asyncFunc().then(function (data) {
$timeout(function () {
$scope.isInside = true;
});
});
}
The whole code:
(function (angular) {
// Create module
var myApp = angular.module('myApp', []);
// Controller which counts changes to its "name" member
myApp.controller('MyCtrl', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {
$scope.isInside = false;
init();
function init() {
asyncFunc().then(function (data) {
$timeout(function () {
$scope.isInside = true;
});
});
}
function asyncFunc() {
return new Promise((resolve, reject) => {
$http.get('https://httpbin.org/get').success((data) => {
resolve(data);
}).error((error) => {
reject(error);
});
});
}
}]);
})(angular);
My intention is to test the $scope.isInside value. This is my test:
describe('myApp', function () {
var scope,
controller;
beforeEach(function () {
module('myApp');
});
describe('MyCtrl', function () {
var $httpBackend, endpointCall, $timeout;
beforeEach(inject(function ($rootScope, $controller, _$httpBackend_, _$timeout_) {
$httpBackend = _$httpBackend_;
$timeout = _$timeout_;
endpointCall = $httpBackend.expect('GET', 'https://httpbin.org/get').respond({data: 'data'});
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope
});
}));
it('initializes', function () {
$httpBackend.flush();
$timeout.flush();
expect(scope.isInside).toBe(true);
});
});
});
The test is failing, the value is "false", but I'm expecting to see a "true" there.
JSFiddle here: http://jsfiddle.net/KarmaCop213/tj6akcyk/1/

AngularJS set scope variables in Karma test

I'm trying to set up Karma unit tests and in my test I want to set a scope variable so I can run the test. I get the error Cannot set property 'expandedSeries' of undefined.
Below is my code. What am I doing wrong?
describe('FormController', function () {
beforeEach(module('userFormApp'));
var $controller;
var $rootScope;
beforeEach(inject(function (_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('$scope.getImageSrc', function () {
var $scope, controller;
beforeEach(function () {
$scope = $rootScope.$new();
controller = $controller('FormController', { $scope: $scope});
});
$scope.expandedSeries = 1;
it('sets variables ', function () {
expect($scope).toBeDefined();
expect($scope.expandedSeries).toBeDefined();
expect($scope.expandedSeries).toEqual(1);
});
});
Instantiate the variable in before each so that the test cases can get it when they start.
describe('$scope.getImageSrc', function () {
var $scope, controller;
beforeEach(function () {
$scope = $rootScope.$new();
controller = $controller('FormController', { $scope: $scope});
$scope.expandedSeries = 1;
});
it('sets variables ', function () {
expect($scope).toBeDefined();
expect($scope.expandedSeries).toBeDefined();
expect($scope.expandedSeries).toEqual(1);
});
});

Angular directive controller unit test using window.confirm

I am trying to get 100% test coverage for a directive. The directive has a controller with a function that uses the window.confirm method.
'use strict';
(function() {
angular
.module('app')
.directive('buttonToggle', buttonToggle);
function buttonToggle() {
var buttonToggleController = ['$scope', function($scope) {
$scope.toggle = function() {
var confirmResponse = (window.confirm('Are you sure?') === true);
if(confirmResponse) {
$scope.on = !$scope.on;
}
return $scope.on;
};
}];
return {
restrict: 'E',
templateUrl: 'client/modules/buttonToggle/buttonToggle.html',
replace: true,
scope: {
on: '='
},
controller: buttonToggleController
};
}
})();
I have tested to make sure that everything is defined, but I am not able to enter the if statement in the controller's $scope.toggle method.
describe('The buttonToggle directive', function() {
var $compile,
$scope,
btElement = '<button-toggle></button-toggle>',
compiledElement,
window,
confirm,
btElementPath = 'client/modules/buttonToggle/buttonToggle.html',
btController;
beforeEach(module('app'));
beforeEach(module(btElementPath));
beforeEach(inject(function(_$compile_, _$rootScope_, $templateCache, $window) {
$compile = _$compile_;
window = $window;
spyOn(window, 'confirm');
$scope = _$rootScope_.$new();
var template = $templateCache.get(btElementPath);
$templateCache.put(btElementPath, template);
var element = angular.element(btElement);
compiledElement = $compile(element)($scope);
$scope.$digest();
btController = element.controller('buttonToggle', {
$window: window
});
scope = element.isolateScope() || element.scope();
}));
it('should be defined', function() {
expect(compiledElement.html()).toContain('btn');
});
describe('buttonToggle controller', function() {
it('should be defined', function() {
expect(btController).not.toBeNull();
expect(btController).toBeDefined();
});
describe('toggle', function() {
it('should be defined', function() {
expect(scope.toggle).toBeDefined();
});
it('should confirm the confirmation dialog', function() {
scope.toggle();
expect(window.confirm).toHaveBeenCalled();
});
});
});
});
I am guessing it has something to do with mocking the $window service, but I'm not sure if I will be able to test that since it isn't declared globally. So, is the controller's function fully "unit testable"? If not, should I write the directive's controller in a separate file and use angular.module.controller? If yes, then how am I able to test it, or what am I missing?
Use angular's $window service instead of window directly, which is what you are doing in your test but not in your directive.
Then you can mock any of its functions:
spyOn($window, 'confirm').and.returnValue(false);

how do we do a unit test a function in an angularJS controller

here is the code:
(function(){
"use strict";
angular.module("dataModule")
.controller("panelController", ["$scope", "$state", "$timeout", "$modal", panelController]);
function panelController($scope, $state, $timeout, $modal){
$scope.property = "panelController";
//how do we do unit test on openCancelWarning.
//i did not find a way to get openCancelWarning function in Jasmine.
function openCancelWarning () {
var cancelModal = $modal.open({
animation: true,
backdrop: "static",
templateUrl: "pages/data/cancel-warning.html",
controller: "cancelWarningController",
size: "sm",
resolve: {
items : function() {
return {
warningTitle : "Are you Sure?",
warningMessage: "There are unsaved changes on this page. are you sure you want to navigate away from this page?Click OK to continue or Cancel to stay on this page"
};
}
}
});
return cancelModal;
}
var resultPromise = openCancelWarning();
var result;
resultPromise.result.then(function(response){
result = response;
});
}
angular.module("dataModule")
.controller("cancelWarningController", ["$scope", "$modalInstance", "items", cancelWarningController]);
function cancelWarningController($scope, $modalInstance, items){
$scope.warningTitle = items.warningTitle;
$scope.warningMessage = items.warningMessage;
$scope.cancel = function() {
$modalInstance.close(false);
};
$scope.ok = function() {
$modalInstance.close(true);
};
}
}());
here is my jasmine unit test code.
describe("Controller: panelController", function () {
beforeEach(module("dataModule"));
var panelController, scope;
var fakeModal = {
result : {
then: function(confirmCallback) {
this.confirmCallback = confirmCallback;
}
},
close: function(confirmResult) {
this.result.confirmCallback(confirmResult);
}
};
beforeEach(inject(function($modal) {
spyOn($modal, "open").andReturn(fakeModal);
}));
beforeEach(inject(function ($controller, $rootScope, _$modal_) {
scope = $rootScope.$new();
panelController = $controller("panelController", {
$scope: scope,
$modal: _$modal_
});
}));
it('test should be true', function () {
var test;
var testResult = panelController.openCancelWarning();
testResult.close(true);
testResult.then(function(response){
test=response;
});
expect(test).toBe(true);
});
});
i wrote above unit test code with the help from Mocking $modal in AngularJS unit tests
i always get below error.
TypeError: 'undefined' is not a function (evaluating 'panelController.openCancelWarning()')
could anyone help this?

Jasmine controller testing, expected spy to have been called

I have a method defined in AngularJS controller which is called on initialization. I want to test it using Jasmine ("jasmine-core": "^2.3.4", "karma": "^0.12.37"). I follow some tutorials on the Internet and StackOverflow questions, but I cannot find the right answer. Please take a look at this code:
Controller usersAddUserController:
(function () {
'use strict';
angular.module('app.users.addUser')
.controller('usersAddUserController', ['$scope', 'usersAddUserService', function ($scope, usersAddUserService) {
usersAddUserService.getCountryPhoneCodes().then(function (phoneCodes) {
$scope.phoneCodes = phoneCodes;
});
}]);
}());
Jasmine test:
(function () {
'use strict';
describe('usersAddUserControllerUnitTest', function () {
var scope, deferred, objectUnderTest, mockedAddUserService;
beforeEach(module('app'));
beforeEach(inject(function ($rootScope, $q, $controller) {
scope = $rootScope.$new();
function emptyPromise() {
deferred = $q.defer();
return deferred.promise;
}
mockedAddUserService = {
getCountryPhoneCodes: emptyPromise
};
objectUnderTest = $controller('usersAddUserController', {
$scope: scope,
usersAddUserService: mockedAddUserService
});
}));
it('should call getCountryPhoneCodes method on init', function () {
//when
spyOn(mockedAddUserService, 'getCountryPhoneCodes').and.callThrough();
deferred.resolve();
scope.$root.$digest();
//then
expect(mockedAddUserService.getCountryPhoneCodes).toHaveBeenCalled();
});
});
}());
After running the tests, the error message is:
PhantomJS 1.9.8 (Windows 7 0.0.0) usersAddUserControllerUnitTest should call getCountryPhoneCodes method on init FAILED
Expected spy getCountryPhoneCodes to have been called.
I obviously missing something, but I cannot figure out what it is. Any help will be appreciated.
You are spying on the mock after it has been passed into the instantiated controller.
Try this:
describe('usersAddUserControllerUnitTest', function () {
var scope, deferred, objectUnderTest, mockedAddUserService, $controller;
beforeEach(module('app'));
beforeEach(inject(function ($rootScope, $q, _$controller_) {
scope = $rootScope.$new();
function emptyPromise() {
deferred = $q.defer();
return deferred.promise;
}
mockedAddUserService = {
getCountryPhoneCodes: emptyPromise
};
$controller = _$controller_;
}));
function makeController() {
objectUnderTest = $controller('usersAddUserController', {
$scope: scope,
usersAddUserService: mockedAddUserService
});
}
it('should call getCountryPhoneCodes method on init', function () {
//when
spyOn(mockedAddUserService, 'getCountryPhoneCodes').and.callThrough();
makeController();
deferred.resolve();
scope.$root.$digest();
//then
expect(mockedAddUserService.getCountryPhoneCodes).toHaveBeenCalled();
});
});
EDIT Thanks #juunas for noticing the bug in my solution
You can provide the mock like this:
mockedAddUserService = {
getCountryPhoneCodes: emptyPromise
};
beforeEach(function () {
module(function ($provide) {
$provide.value('usersAddUserService', mockedAddUserService);
});
});
EDIT:
The code should look (as i cannot test it) like this:
(function () {
'use strict';
describe('usersAddUserControllerUnitTest', function () {
beforeEach(module('app'));
var emptyPromise = function() {
var deferred = $q.defer();
return deferred.promise;
}
var mockedAddUserService = {
getCountryPhoneCodes: emptyPromise
};
beforeEach(function () {
module(function ($provide) {
$provide.value('usersAddUserService', mockedAddUserService);
});
});
var scope;
beforeEach(inject(function ($rootScope, $q, $controller) {
scope = $rootScope.$new();
$controller('usersAddUserController', {
$scope: scope
});
}));
it('should call getCountryPhoneCodes method on init', function () {
spyOn(mockedAddUserService, 'getCountryPhoneCodes').and.callThrough();
scope.$root.$digest();
expect(mockedAddUserService.getCountryPhoneCodes).toHaveBeenCalled();
});
});
}());

Categories

Resources