AngularJS + Karma + Jasmine Controller Unit testing : - angularjs

I have the following unit test code in which first test is passing, but the second one is failing and I am not able to figure it out.
Unit test code
describe('List controller', function () {
beforeEach(angular.mock.module('myApp'));
var $controller, $rootScope, $scope, $state;
beforeEach(inject(function (_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
}));
describe('Parameter Group List', function() {
it('should exists', function () {
$controller = $controller('baseController', {'$scope': $scope});
expect($scope.prepareSidebarMenu).toBeDefined();
});
it('should exists', function () {
$state = {};
$scope.header = {};
$state.current = {};
var parameterGroups = '';
$scope.header.title = '';
$controller = $controller('listController', {'$scope': $scope, 'parameterGroups': parameterGroups });
expect($scope.truncateCharacters).toBeDefined();
});
});
});
and I am trying to test following controllers.
Base controller
(function () {
'use strict';
angular.module('PpmApp')
.controller('baseController', ['$scope', '$injector', baseController]);
function baseController($scope, $injector) {
var $state = $injector.get('$state');
$scope.header = {
title: "Plan Parameter Manager"
};
$scope.sidebarMenuInfo = {
name: $state.current.name,
parameterGroupId: null,
hideSidebar: true
};
$scope.prepareSidebarMenu = function (sidebarMenuInfo) {
angular.extend($scope.sidebarMenuInfo, sidebarMenuInfo);
};
}
})();
List controller
(function () {
'use strict';
angular.module('PpmApp')
.controller('listController', ['$scope', '$injector', 'parameterGroups', listController]);
function listController($scope, $injector, parameterGroups) {
var $state = $injector.get('$state');
var apiService = $injector.get('apiService');
var utilService = $injector.get('utilService');
var constantsProvider = $injector.get('constantsProvider');
$scope.header.title = "Parameter Manager Overview";
$scope.prepareSidebarMenu({
name: $state.current.name,
hideSidebar: true
});
var titleMaxLength = 47;
var descriptionMaxLength = 270;
$scope.parameterGroups = parameterGroups;
$scope.createPopupInfo = {};
$scope.createPopupInfo.validationErrors = {};
$scope.createPopupInfo.validationErrors.isError = false;
$scope.createPopupInfo.years = utilService.populateYearsForParameterGroup();
$scope.createPopupInfo.months = constantsProvider.months;
$scope.truncateCharacters = function (text, type) {
if (type == 'title' && text.length > titleMaxLength) {
return text.substring(0, titleMaxLength) + '...';
} else if (type == 'description' && text.length > descriptionMaxLength) {
return text.substring(0, descriptionMaxLength) + '...';
}
return text;
};
}
})();
My Unit test result is
$ karma start
07 02 2018 12:41:00.734:WARN [karma]: No captured browser, open http://localhost :9876/
07 02 2018 12:41:00.746:INFO [karma]: Karma v0.13.22 server started at http://lo calhost:9876/
07 02 2018 12:41:00.751:INFO [launcher]: Starting browser Chrome
07 02 2018 12:41:02.946:INFO [Chrome 63.0.3239 (Windows 7 0.0.0)]: Connected on socket doxoZRvKoJYdYozhAAAA with id 67949004
parameterGroupListController
Parameter Group List
√should exists
×should exists
TypeError: $scope.prepareSidebarMenu is not a function
at new parameterGroupListController (C:/ZS/JIM-PPM/PlanManagerWeb/Ji mPPM/app/controllers/parameter-group-list.js:14:16)
at Object.instantiate (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/resources /angular.js:5112:14)
at $controller (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/resources/angula r.js:11083:28)
at C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/node_modules/angular-mocks/an gular-mocks.js:2314:14
at UserContext.<anonymous> (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/test s/parameter-group.spec.js:24:27)
Chrome 63.0.3239 (Windows 7 0.0.0): Executed 2 of 2 (1 FAILED) (0.038 secs / 0 s ecs)
TOTAL: 1 FAILED, 1 SUCCESS
1) should exists
parameterGroupListController Parameter Group List
TypeError: $scope.prepareSidebarMenu is not a function
at new parameterGroupListController (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/app /controllers/parameter-group-list.js:14:16)
at Object.instantiate (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/resources/angular .js:5112:14)
at $controller (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/resources/angular.js:110 83:28)
at C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/node_modules/angular-mocks/angular-mo cks.js:2314:14
at UserContext.<anonymous> (C:/ZS/JIM-PPM/PlanManagerWeb/JimPPM/tests/parame ter-group.spec.js:24:27)

listController expects that prepareSidebarMenu is defined on scope, and it's not. If it is supposed to be defined by baseController and inherited from parent scope, it should be stubbed in listController test:
$scope.prepareSidebarMenu = jasmine.createSpy();
$controller = $controller('listController', {'$scope': $scope, 'parameterGroups': parameterGroups });
expect($scope.prepareSidebarMenu).toHaveBeenCalledWith(...);
expect($scope.truncateCharacters).toBeDefined();

Related

defining current state in unit tests

I've moved this 'reports' feature from a single module (called 'aam') into the core, so that other modules (such as 'bbc') can use it.
Now I'm rewriting the unit test(s).
The grunt error I'm getting is
should go state aam.reports with URL_NOT_SPECIFIED
reports-state spec
TypeError: 'null' is not an object
(evaluating 'BbpcConfiguration.getProperty(configProperty).then')
which indicates to me that $state is empty or not structured correctly.
Here is the report controller:
(function() {
'use strict';
angular.module('com.ct.bbpcCore')
.controller('reportController', ['$window', '$state', 'BbpcUserService', 'BbpcConfiguration', function ($window, $state, BbpcUserService,BbpcConfiguration) {
angular.element(document).ready(function () {
//Get url base on locale
var reportUrl = "URL_NOT_SPECIFIED";
var currentState = $state.current.name;
var configProperty = "";
var title = "";
if (currentState.indexOf('aam.reports')) {
configProperty = 'report.aam.link';
title = "AAM.REPORT";
};
if (currentState.indexOf('bbc.reports')) {
configProperty = 'report.bbc.link';
title = "BBC.REPORT";
};
BbpcConfiguration.getProperty(configProperty).then(function(response) {
if (response) {
var language = BbpcUserService.getLanguageCd() || "en_CA";
reportUrl = response[language] || reportUrl;
}
var spec = "width=" + $window.outerWidth + ", height=" + $window.outerHeight;
$window.open(reportUrl, title, spec);
});
});
}]);
}());
And here is report-controller.spec:
describe('reports-state spec', function() {
'use strict';
var $injector, $window, $rootScope,
$state, BbpcConfiguration, reportController, $controller, BbpcUserService;
beforeEach(function() {
module('com.ct.bbpcCore', function($provide) {
$provide.value('BbpcConfiguration', BbpcConfiguration = {
getProperty: function(key){
if('report.aam.link' === key){
return {
"fr_CA": "https://eng-link",
"en_CA": "https://fre-link"
};
}
return null;
}
});
});
inject(function(_$injector_) {
$injector = _$injector_;
$window = $injector.get('$window');
$state = $injector.get('$state');
$rootScope = $injector.get('$rootScope');
$controller =$injector.get('$controller');
BbpcUserService =$injector.get('BbpcUserService');
});
});
it('should go state aam.reports with URL_NOT_SPECIFIED', function() {
$state.current = {'name': 'aam.reports' };
spyOn($window, 'open').andCallFake(function(){});
reportController = $controller('reportController', {'$window':$window, '$state':$state, 'BbpcUserService':BbpcUserService, 'reportLink':undefined});
$state.go('aam.reports');
$rootScope.$apply();
expect($state.current.name).toEqual('aam.reports');
expect($window.open).toHaveBeenCalledWith('URL_NOT_SPECIFIED', 'AAM.REPORT', 'width=0, height=0');
});
});
I tried simply adding the line $state.current = {'name': 'aam.reports' }; in the 'it' block, but that's not what it's looking for.
Not sure how to debug unit tests. :P I can't use a console.log($state) to peek into it.

Error in Karma/Jasmine Unit test

This is my AngularJS code:
angular.module('myapp', [])
.controller('MainCtrl', function($scope, $http, $rootScope, $routeParams) {
$scope.name = "Girish";
$scope.sayHello = function() {
$scope.greeting = "Hello " + $scope.name;
};
$scope.commonUrl = "/";
$rootScope.$watch('eng', function() {
$scope.currentLang = $rootScope.CURRENT_LANGUAGE;
});
$scope.homePageFirstSlider = function() {
$scope.anun = "Nar";
$http({
method: 'GET',
url: "/" + "eng" + '/api/getslideritems/main'
}).then(function successCallback(response) {
$scope.Data = response.data;
$scope.loadCss('mainCarousel');
},
function errorCallback(response) {});
};
});
This is a test file:
'use strict';
describe('myapp', function() {
beforeEach(module('myapp'));
var $controller;
beforeEach(inject(function(_$controller_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.sayHello', function() {
it('same tests', function() {
var $scope = {};
var controller = $controller('MainCtrl', { $scope: $scope });
$scope.name = 'Girish';
$scope.sayHello();
expect($scope.greeting).toEqual('Hello Girish');
});
});
});
After I run karma.conf.js file I have this error:
PhantomJS 2.1.1 (Linux 0.0.0) myapp $scope.sayHello same tests FAILED
***Error: [$injector:unpr] Unknown provider: $routeParamsProvider <- $routeParams <- MainCtrl***
http://errors.angularjs.org/1.5.0/$injector/unpr?p0=%24routeParamsProvider%20%3C-%20%24routeParams%20%3C-%20MainCtrl in /var/www/html/famboxv2/public/bower_components/angular/angular.js (line 4397)
loaded#http://localhost:9882/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.025 secs / 0.004 secs)
Chromium 45.0.2454 (Ubuntu 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.008 secs / 0.005 secs)
How can I solve this error?
Unknown provider: $routeParamsProvider <- $routeParams <- MainCtrl
$routeParams service is a part of ngRoute module. So you'll have to include it as a dependency on your module.
Here's how:
angular.module('myapp', ['ngRoute'])
Also you should be mocking the controller in the first beforeEach block instead of mocking it inside a specific test, so that it could be reusable in all your tests.
And your tests will still be failing because you've inject an empty object for $scope instead of creating a new scope using the $rootScope's $new function. So you should be doing the following changes to make them pass:
describe('myapp', function() {
beforeEach(module('myapp'));
//Replace this
beforeEach(inject(function($controller, _$rootScope_){
//With this
// beforeEach(inject(function($controller, _$rootScope_, _$routeParams_, _$http_){
//Uncomment the following comments if needed for writing tests for the $watch function.
// $rootScope = _$rootScope_;
$scope = _$rootScope_.$new();
// $http = _$http_;
// $routeParams = _$routeParams_;
controller = $controller('MainCtrl', {
$scope: $scope,
// $http: $http,
// $rootScope: $rootScope,
// $routeParams: $routeParams
});
}));
describe('$scope.sayHello', function() {
it('same tests', function() {
$scope.name = 'Girish';
$scope.sayHello();
expect($scope.greeting).toEqual('Hello Girish');
});
});
});

Error "Function expected" when mocking localStorage.getItem

I'm trying to mock calls to localStorage.getItem, but getting error "Function expected". Here is my test code:
describe("AuthService Tests", function() {
var authSvc, httpBackend, scope;
var fakeItem = "{\"access_token\":\"giant_access_token\",\"token_type\":\"bearer\",\"expires_in\":1209599,\".issued\":\"Thu, 11 Feb 2016 13:22:45 GMT\",\".expires\":\"Thu, 25 Feb 2016 13:22:45 GMT\",\"accountType\":\"Administrator\",\"certifiedForAccess\":true}";
var returnFakeToken = function(key) { return fakeItem; }
beforeEach(module('app'));
beforeEach(inject(function (_AuthService_, $q, $rootScope, $httpBackend, $state, _config_, _messages_) {
scope = $rootScope.$new();
spyOn(localStorage, 'getItem').and.callFake(returnFakeToken);
authSvc = _AuthService_;
httpBackend = $httpBackend;
}));
describe('Test suite 1', function () {
it('should return true if user is Administrator', function () {
var result = authSvc.isAdministrator(); // error here
expect(result).toBe(true);
});
});
});
Service under test:
(function () {
'use strict';
angular
.module('app.services')
.factory('AuthService', AuthService);
AuthService.$inject = ['$q', '$rootScope', '$http', '$state', 'config', 'messages'];
function AuthService($q, $rootScope, $http, $state, config, messages) {
var userIdKey = 'userId';
var tokenKey = 'tokenKey';
var userAuthKey = 'userAuth';
var svc = {
isAdministrator: isAdministrator
};
return svc;
function isAdministrator() {
var item = localStorage.getItem(tokenKey); // eror here
var jsonparse = JSON.parse(item);
return jsonparse.accountType == 'Administrator';
}
})();
When I run tests, my mocked function returnFakeToken hadn't even been called and I got error (lines of code where error occured are marked with comments):
TypeError: Function expected
What am I doing wrong?
Thanks!
You don't need to spy on localStorage. It is a function that is built into the browser as part of the HTML5 standard. What you should be testing is that your service returns the expected value. Also, you may need to mock out localStorage if you are testing in PhantomJS.

Flushing successful mock POST request using Jasmine does not execute the AngularJS success function

This is my AngularJS post.js:
angular.module("PostPageApp", ["BaseApp"])
.controller("MainCtrl", ["$http", "$window", "BaseService", function($http, $window, BaseService) {
var self = this;
self.add = function() {
BaseService.add.post(self.post, function() {
self.cerrorMessages = BaseService.cerrorMessages;
});
};
}]);
This is base.js:
angular.module("BaseApp", [])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}])
.config(['$locationProvider', function($locationProvider){
$locationProvider.html5Mode(true);
}])
.factory("BaseService", ["$http", "$window", function($http, $window) {
var self = this;
self.posts = [];
self.cerrorMessages = [];
/* This function sets self.cerrorMessages. After calling this function,
* you should do a callback to a function on the front-end which
* sets cerrorMessage. */
self.accessErrors = function(data) {
self.cerrorMessages = [];
for (prop in data) {
if (data.hasOwnProperty(prop)){
/* if (data[prop] != null && data[prop].constructor == Object) {
self.accessErrors(data[prop]);
}
else { */
self.cerrorMessages.push(data[prop]);
// }
}
}
};
self.add = {
post: function(post, callback) {
$http.post("/posts/", post)
.then(function(response) {
$window.location.href = "/";
}, function(response) {
self.accessErrors(response.data);
callback();
});
}
};
return self;
}]);
And this is my test_post.js:
describe('Controller: MainCtrl', function() {
beforeEach(module('PostPageApp'));
var ctrl, $loc;
beforeEach(inject(function($controller, $location, $httpBackend, BaseService) {
ctrl = $controller('MainCtrl');
$loc = $location;
mockBackend = $httpBackend;
spyOn(BaseService, 'add').and.callThrough();
baseService = BaseService;
}));
it('should have an add function', function() {
expect(ctrl.add).toBeDefined();
});
it('should be able to create a post object', function() {
$loc.path('/post');
ctrl.post = {'post':'Test post'}
mockBackend.expectPOST('/posts/', ctrl.post)
.respond(201, {'post':'TestPost', 'posting': 1});
ctrl.add();
mockBackend.flush();
expect(baseService.add).toHaveBeenCalled();
expect($loc.path()).toEqual('/');
expect(ctrl.cerrorMessages).toBeUndefined();
});
});
Now, when I run karma start, it returns this:
Chromium 47.0.2526 (Ubuntu 0.0.0) Controller: MainCtrl should be able to create a valid post object FAILED
Expected spy add to have been called.
at Object.<anonymous> (/home/u/Documents/CMS/CMSApp/static/js/karma/tests/test_post.js:32:33)
Expected '/post' to equal '/'.
at Object.<anonymous> (/home/u/Documents/CMS/CMSApp/static/js/karma/tests/test_post.js:33:29)
Chromium 47.0.2526 (Ubuntu 0.0.0): Executed 3 of 3 (1 FAILED) (0 secs / 0.119 secChromium 47.0.2526 (Ubuntu 0.0.0): Executed 3 of 3 (1 FAILED) (0.163 secs / 0.119 secs)
As you can see, spy add was expected to be called, but since Expected spy add to have been called. was printed on the terminal, from my understanding, this means that it was not called, right?
Also, it also printed Expected '/post' to equal '/'. onto the terminal so this also means that the URL is still at '/post', correct?
Any idea why the URL did not change and spy add was not called?
If you're testing the controller, only test the controller. That's why it's called a unit test. You should be mocking any external services.
describe('Controller: MainCtrl', function() {
var ctrl, mockBaseService;
beforeEach(function() {
mockBaseService = {
cerrorMessages: 'whatever',
add: jasmine.createSpyObj('BaseService.add', ['post'])
};
mockBaseService.add.post.and.callFake(function(something, cb) {
cb(); // execute the callback immediately
});
module('PostPageApp');
inject(function($controller) {
ctrl = $controller('MainCtrl', {
BaseService: mockBaseService
});
});
});
it('add calls through to BaseService.add.post', function() {
ctrl.post = 'something'; // just adding this because I can't see it anywhere else
ctrl.add();
expect(mockBaseService.add.post).toHaveBeenCalledWith(ctrl.post, jasmine.any(Function));
expect(ctrl.cerrorMessages).toBe(mockBaseService.cerrorMessages);
});
});

Jasmine service error : Unknown provider: domSvcProvider <- domSvc

My actual controller has a few more services, I made a strip down version in an attempt to track down this service injection issue.
Any ideas?
var testApp = angular.module("testApp",[]);
testApp.controller('testCtrl',['$scope','domSvc',testCtrl]);
function testCtrl($scope,domSvc){
$scope.testone = function(){
return "testone";
};
}
----------
describe('Main tests', function() {
beforeEach(angular.mock.module('testApp'));
var $controller,
$injector,
domSvc;
beforeEach(angular.mock.inject(function(_$controller_,_$injector_){
$controller = _$controller_;
$injector = _$injector_;
}));
it('testone should return the string testone',function(){
var $scope = {};
var domSvc = $injector.get('domSvc');
var controller = $controller('testCtrl',{$scope : $scope, domSvc : domSvc});
expect($scope.testone()).toEqual('testone');
});
});

Resources