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/
Related
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();
});
});
});
I have a following piece of code (simplified):
angular
.module('myApp')
.controller('MyController', MyController);
function MyController(wordService) {
getWord();
function getWord() {
return wordService.getNextWord()
.then(doSomethingWithWord)
.catch(doSomethingFailure);
function doSomethingWithWord(response) {
// ... something
}
function doSomethingFailure() {
// ... failing
}
}
}
And I have to test it.
I'm struggling with this over a day now and I can't get it working :(
How to test this code?
For the future, I figured it out:
I have to use $q service and request Angular digest cycle.
describe('MyController', function () {
var $controller, myController, wordService, $q, deferredResponse, scope;
beforeEach(function() {
module('myApp');
inject(function(_$controller_, _wordService_, _$q_, $rootScope) {
$controller = _$controller_;
wordService = _wordService_;
scope = $rootScope.new();
$q = _$q_;
});
myController = $controller('MyController', {wordService:wordService});
deferredResponse = $q.defer(); //deferring asynchronous response
spyOn(wordService, 'getNextWord').and.returnValue(deferredResponse.promise);
});
describe('Testing WordService', function() {
it('Should get next word', function () {
deferredResponse.resolve({status: 200, data: {word: 123}});
scope.$apply();
expect(wordService.getNextWord).toHaveBeenCalled();
})
})
});
I looked into examples on how to do this properly but it's definitely not updating on my end. I put a breakpoint to make sure it's updating and going through the timer in the Factory and it's updating properly. I shouldn't have to use $watch right? If someone can help me figure out what's going on it would help with my headache right now lol thanks.
Factory
app.factory('FoundationSystemStatusFactory', ['$timeout', '$q', 'SystemStatusFactory', function ($timeout, $q, SystemStatusFactory) {
var service = {};
service.Count = 0;
service.Ping = 0;
service.PollingTest = function() {
$timeout(function () {
SystemStatusFactory.PingIP('www.google.com')
.then(function (data) {
service.Ping = data.data;
service.Count++;
}, function (data) {
service.Ping = data.data;
});
service.PollingTest();
}, 2000);
}
return service;
}]);
Controller
FoundationSystemStatusFactory.PollingTest();
$scope.ping = FoundationSystemStatusFactory.Ping; //NOT UPDATING
$scope.count = FoundationSystemStatusFactory.Count; //NOT UPDATING
EDIT: tried as Service, still couldn't get it to work:
var self = this;
self.Count = 0;
self.Ping = 0;
self.PollingTest = function () {
$timeout(function () {
SystemStatusFactory.PingIP('www.google.com')
.then(function (data) {
self.Ping = data.data;
self.Count++;
}, function (data) {
self.Ping = data.data;
});
self.PollingTest();
}, 2000);
}
A different approach - events.
app.factory('FoundationSystemStatusFactory', ['$rootScope', '$timeout', '$q', 'SystemStatusFactory', function ($rootScope, $timeout, $q, SystemStatusFactory) {
var service = {
Count: 0
};
service.PollingTest = function() {
$timeout(function () {
SystemStatusFactory.PingIP('www.google.com')
.then(function (data) {
$rootScope.$broadcast('FoundationSystemStatus:ping', data.data);
service.Count++;
}).catch(function (data) {
$rootScope.$broadcast('FoundationSystemStatus:ping', data.data);
});
service.PollingTest();
}, 2000);
}
return service;
}]);
//On controller...
$scope.$on('FoundationSystemStatus:ping', function(ping){
$scope.ping = ping;
});
You can use watcher:
$scope.$watch('FoundationSystemStatusFactory.Ping', function(newValue) {
$scope.ping = newValue;
});
Or you can use reference to factory:
$scope.status = FoundationSystemStatusFactory;
$interval(function() {
console.log($scope.status.Ping); // gets updated
});
Okay I found out how to do it after some more research. Objects are referenced as numbers and strings are not.
Factory
app.factory('FoundationSystemStatusFactory', ['$timeout', '$q', 'SystemStatusFactory', function ($timeout, $q, SystemStatusFactory) {
var service = {};
service.Data = {
Count: 0,
Ping: 0
}
service.PollingTest = function() {
$timeout(function () {
SystemStatusFactory.PingIP('www.google.com')
.then(function (data) {
service.Data.Ping = data.data;
service.Data.Count++;
}, function (data) {
service.Data.Ping = data.data;
});
service.PollingTest();
}, 2000);
}
return service;
}]);
Controller
app.controller('SystemStatusController', ['$scope', '$rootScope', '$timeout', 'FoundationSystemStatusFactory',
function ($scope, $rootScope, $timeout, FoundationSystemStatusFactory) {
FoundationSystemStatusFactory.PollingTest();
$scope.data = FoundationSystemStatusFactory.Data;
}]);
View
{{data.Ping}}
{{data.Count}}
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?
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();
});
});
}());