Jasmine Testing angularjs controller - angularjs

I am trying to test my controller but I get undefined when running karma start
This is my controller Code:
(function() {
'use strict';
angular
.module('tariffOptionsExtras')
.controller('tariffOptionsExtrasBookCtrl', tariffOptionsExtrasBookCtrl);
tariffOptionsExtrasBookCtrl.$inject = ['tariffOptionsExtrasSrv', '$location', 'cache', 'authSrv', '$stateParams',
'tariffOptionsExtrasBookSrv', 'minimumDuration', '$rootScope', 'userTypes', 'messageCtrl', '$state'];
function tariffOptionsExtrasBookCtrl(tariffOptionsExtrasSrv, $location, cache, authSrv, stateParams, tariffOptionsExtrasBookSrv, minimumDuration, $rootScope, userTypes, messageCtrl, $state) {
var vm = this;
var cacheName = 'tariffOptionsExtras';
var cacheCollection = cache.getCollection(cacheName);
vm.date = new Date();
vm.minimumDuration = minimumDuration;
//if statement commented out. prepaid users should be able to enter the flow.
Date(vm.bookOption.startDate)
}
if (!vm.bookOption) {
$state.go(pagesConfig.tariffOptionsExtras.name);
} else {
vm.infoLink = vm.bookOption.infoUrl;
}
/**
* Runs booking tarif extra post and in case of success the view is changed
*/
vm.book = function() {
//If bookoption not present, redirect to chooser
tariffOptionsExtrasBookSrv.bookTariffExtra(vm.bookOption).then(function(response) {
$rootScope.$broadcast('transactionFinished');
var item = response['salesOrderVBO'][0]['orderItems']['orderItem'][0];
if (item.product.action == 'BOOKED') {
vm.success = true;
}
}, function(reason) {
vm.errorMessage = reason.data.faultMessage;
});
};
vm.success = false;
vm.subscription = authSrv.getUserContract();
vm.msisdn = vm.subscription.subscription.msisdn;
}
})();
Unit Test with jasmine
describe('tariffOptionsExtras module', function() {
describe('tariffOptionsExtrasBook Controller', function() {
var tariffOptionsExtrasBookCtrl, tariffOptionsExtrasSrv, tariffOptionsExtrasBookSrv, authSrv;
// Step 1: Import the module this controller belongs to, and its dependencies
beforeEach(function() {
module('app.common');
module('tariffOptionsExtras');
});
// Step 2: Mock any service that initially used when the controller instantiate
// beforeEach(module(function($provide) {
// $provide.factory('tariffOptionsExtrasBookSrv', function() {
// var getSync;
// getBookedExtras = function() {
// return {
// then:function(){}
// };
// };
// getTariffBookableOptions = function() {
// return {
// then:function(){}
// };
// };
// return {
// getBookedExtras: getBookedExtras,
// getTariffBookableOptions:getTariffBookableOptions
// };
// });
// }));
beforeEach(module(function($provide) {
$provide.factory('authSrv', function() {
getUserContract = function() {
return {
subscription:{
msisdn:'491741660390',
mboName:'27434975'
},
contract:{
mboName:'27434975',
ban:'106491816',
}
};
};
return {
getUserContract: getUserContract,
};
});
}));
// Step 3: Inject $controller with its dependencies
beforeEach(function() {
// 1. Import the module
module('app');
// 2. Inject $controller
inject(function($controller, $rootScope, _authSrv_, _tariffOptionsExtrasSrv_, _tariffOptionsExtrasBookSrv_) {
authSrv = _authSrv_;
tariffOptionsExtrasSrv = _tariffOptionsExtrasSrv_;
tariffOptionsExtrasBookSrv = _tariffOptionsExtrasBookSrv_;
// 3. Use $controller to instantiate the controller
tariffOptionsExtrasBookCtrl = $controller('tariffOptionsExtrasBookCtrl', {
'authSrv': authSrv,
'tariffOptionsExtrasSrv': tariffOptionsExtrasSrv,
'tariffOptionsExtrasBookSrv': tariffOptionsExtrasBookSrv
});
});
});
// Step 4: Test the controller
it('Should return sum of 1+1', function() {
expect(1+1).toBe(2);
});
});
});
When running karma Start I get this screen:
enter image description here
Also when I comment this block of code it works:
tariffOptionsExtrasBookCtrl = $controller('tariffOptionsExtrasBookCtrl', {
'authSrv': authSrv,
'tariffOptionsExtrasSrv': tariffOptionsExtrasSrv,
'tariffOptionsExtrasBookSrv': tariffOptionsExtrasBookSrv
});

first you have to inject the $controller param must be enclosed with underscores
then assign it to the $controller variable as this example
beforeEach(inject(function ($rootScope, _$controller_) {
scope = $rootScope.$new();
$controller = _$controller_;
$controller('nameController', {$scope: scope});
}));

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.

How to test $scope.$watch properly with Jasmine in AngularJS

I'm new to AngularJS and unit testing,
I'm testing a list that gets changing by selected category.
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
I also tried to use the scope.$digest() but I got the same results...
The Controller:
app.controller('mainCtrl', ['$scope', 'myService', function($scope,
myService) {
$scope.category = null;
myService.getSomethingElse().then(function(res) {
$scope.somethingElse = res.data;
});
$scope.$watch('category', function() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
});
}]);
The Service:
app.service('myService', ['$http', function($http) {
this.getListByCat = function(category) {
return $http.get('getting-list?cat=' + category);
};
this.getLongList = function() {
return $http.get('getting-long-list');
};
this.getSomethingElse = function() {
return $http.get('getting-something-else');
};
}]);
The Test
describe('Testing mainCtrl', function() {
var scope, ctrl;
var myServiceMock = {
getSomethingElse: jasmine.createSpy().and.returnValue(1),
getListByCat: jasmine.createSpy().and.returnValue(2)
};
beforeEach(function() {
module('app');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
it('should update the list by selected category', function() {
expect(scope.category).toBeNull();
expect(scope.list).toBeUndefined();
scope.category = {
id: 1,
name: 'Jobs'
};
scope.$apply();
expect(myServiceMock.getSomethingElse).toHaveBeenCalled();
expect(myServiceMock.getListByCat).toHaveBeenCalled();
});
});
The test is passing but only if I use the httpBackend.expectGET() that expects the XMLHttpRequest from the "getSomethingElse" method.
This is because your myServiceMock is not replacing the original myService. You have various ways to test this - one of them is given below. Here we are replacing myService with the service mock:-
beforeEach(function() {
module('app');
module(function($provide){
$provide.factory('myServiceMock',
function(){
return myServiceMock;
);
});
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
myService: myServiceMock
});
});
});
You can add your watcher like this.
$scope.categoryWatcher = categoryWatcher;
$scope.$watch('category', categoryWatcher);
function categoryWatcher() {
if ($scope.category !== null) {
myService.getListByCat($scope.category.name).then(function(res) {
$scope.list = res.data;
});
}
else {
myService.getLongList().then(function(res) {
$scope.list = res.data;
});
}
}
and in Unit testing just create new it construct for that handler
it('should test categoryWatcher for null value', function(){
$scope.category = null;
$scope.categoryWatcher();
// your expectations
});
it('should test categoryWatcher for "desiredValue" value', function(){
$scope.category = "desiredValue";
$scope.categoryWatcher();
// your expectations
});
that way, if&else clauses will be taken in the test.

How to restrict to invoke service dependencies while writing Unit test for Controller?

I have the following controller:
(function () {
"use strict";
angular.module('usp.configuration').controller('ConfigurationController', ConfigurationController);
ConfigurationController.$inject = ['$scope', '$rootScope', '$routeParams', 'configurationService'];
function ConfigurationController($scope, $rootScope, $routeParams, configurationService) {
//Get Master Gas List
configurationService.getMasterGasList().then(function (response) {
$scope.masterGasList = response.data.data;
});
$scope.convertToInt = function (str) {
if (!isNumberEmpty(str) && !isNaN(str)) {
return parseInt(str, 10);
}
return "";
}
$scope.convertToString = function (num) {
if (!isNumberEmpty(num) && !isNaN(num)) {
return num + "";
}
return "";
}
}
}());
And below is the test case for the controller:
describe("test suite for Configuration test controller", function() {
var scope = null;
var configurationService;
beforeEach(module("usp.configuration"));
beforeEach(inject(function($rootScope, $controller, _configurationService_) {
// Services
// _'s are automatically unwrapped
configurationService = _configurationService_;
// Controller Setup
scope = $rootScope.$new();
$controller("ConfigurationController", {
$scope: scope,
configurationService : configurationService
});
}));
it("should convert to int", function() {
expect(scope.convertToInt("2")).toEqual(2);
});
it("should return empty string", function() {
expect(scope.convertToInt("asd")).toEqual("");
});
});
I don't want to call that service while I am running the test case.
I am new to unit testing, I don't know how can I do this.
Please help me to do this?
You need to mock the dependencies with $provide
beforeEach(function () {
configurationServiceMock = {
getSomething: function () {
return 'mockReturnValue';
}
};
module(function ($provide) {
$provide.value('configurationService', configurationServiceMock);
});
});
see: Injecting a mock into an AngularJS service
Solution for your needs:
var configurationServiceMock = {
getMasterGasList: function () {
return {
then: function(callback) {}
};
}
};
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('ConfigurationController', {
'$scope': scope,
'configurationService': configurationServiceMock
});
}));

JS Unit Testing Using karma and Jesmine

I have written a unit test like below
describe('modals', function() {
beforeEach(module('DetailsApp'));
var controller, rootScope, templateCache, compile, http, httpBackend;
var uibModalInstance = {
dismiss: function(message) {
},
close: function(message) {
}
};
var plugins = {
get: function(plugin) {
if(plugin == 'Workorder'){
return {
'workorder_id': 'workorder_id'
};
} else if(plugin == 'CompanyInfo'){
return {
'company_name': 'company_name',
'company_id': 'company_id'
};
}
}
};
beforeEach(module(function($provide) {
$provide.value('$uibModalInstance', uibModalInstance);
$provide.value('plugins', plugins);
}));
beforeEach(inject(function($controller, $templateCache, $compile, $rootScope, $http, $httpBackend) {
controller = $controller;
templateCache = $templateCache;
compile = $compile;
rootScope = $rootScope;
http = $http;
httpBackend = $httpBackend;
}));
describe('When modal functions are called', function() {
it('they should be called correctly', function() {
var $scope = {};
var companyRatingHistory = controller('companyRatingHistory', { $scope: $scope });
spyOn(uibModalInstance, 'dismiss');
spyOn(uibModalInstance, 'close');
$scope.cancel();
expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel');
$scope.close('close');
expect(uibModalInstance.close).toHaveBeenCalledWith('close');
});
}); });
and found that my code coverage shows an E in plugins else block like below
else if(plugin == 'CompanyInfo'){
return {
'company_name': 'company_name',
'company_id': 'company_id'
};
}
What i have missed in my test. Advance thanks and get any suggestions from anybody who helps me.

Angular Unit Test Jasmine Spy error

The following controller is getting a TypeError: 'undefined' is not a function (evaluating sessionService.getCurrentPlace()). I have a Mock Service with that method being spied on. The other method on the mock service works fine. I've tried .AndReturns({..}) on the spy as well as .AndCallThrough() but no luck. Any idea what I'm missing, or am I going about this wrong? Much Thanks!
CONTROLLER:
'use strict';
angular.module('wallyApp')
.controller('addGatewayCtrl', function ($scope, $location, $filter, sessionService) {
/*
private members
*/
//set scope from session data
$scope.processSession = function (places) {
$scope.currentPlaceId = sessionService.getCurrentPlace();
if (!$scope.currentPlaceId) {
$scope.currentPlaceId = places[0].id;
}
$scope.place = $filter("getById")(places, $scope.currentPlaceId);
$scope.ready = true;
};
/*
setup our scope
*/
$scope.currentPlaceId = null;
$scope.place = {};
$scope.videoSrc = "/videos/gateway-poster.gif";
$scope.loaded = true;
/*
setup controller behaivors
*/
//set video or gif to show or hide video
$scope.setVideo = function () {
$scope.videoSrc = "/videos/gateway.gif";
};
$scope.setPoster = function () {
$scope.videoSrc = "/videos/gateway-poster.gif";
};
//initialize scope
$scope.setVideo();
//submit form
$scope.continue = function () {
$location.path("/setup/pair-gateway");
return false;
};
//cancel
$scope.back = function () {
$location.path("/setup/plan-locations");
return false;
};
//wifi
$scope.gotoWifi = function () {
$location.path("/setup/wifi");
return false;
};
/*
setup our services, etc
*/
//get our places from the cache
sessionService.get("places").then(function (places) {
if (!places || places.length < 1) {
sessionService.refreshPlaces(); //Note we don't care about the promise as our broadcast watch will pick up when ready
} else {
$scope.processSession(places);
}
}).catch(function (error) {
//TODO:SSW Call Alert Service??
});
//Watch broadcast for changes
$scope.$on("draco.placesRefreshed", function (event, data) {
sessionService.get("places").then(function (places) {
$scope.processSession(places);
});
});
});
UNIT TEST:
'use strict';
describe('addGatewayCtrl', function () {
var $q,
$rootScope,
$location,
$scope,
$filter,
mockSessionService,
completePath = "/setup/pair-gateway",
backPath = "/setup/plan-locations",
wifiPath = "/setup/wifi",
sessionDeferred,
sessionInitDeferred,
mockPlaces = [{ id: "0001" }];
beforeEach(module('wallyApp'));
beforeEach(inject(function (_$q_, _$rootScope_, _$location_, _$filter_) {
$q = _$q_;
$location = _$location_;
$rootScope = _$rootScope_;
$filter = _$filter_;
}));
beforeEach(inject(function ($controller) {
$scope = $rootScope.$new();
mockSessionService = {
get: function (contact) {
sessionDeferred = $q.defer();
return sessionDeferred.promise;
},
getCurrentPlace: function () {
return mockPlaces[0].id;
},
refreshPlaces: function () {
sessionInitDeferred = $q.defer();
return sessionInitDeferred.promise;
}
};
spyOn(mockSessionService, 'get').andCallThrough();
spyOn(mockSessionService, 'getCurrentPlace').andReturn(mockPlaces[0].id);
spyOn(mockSessionService, 'refreshPlaces').andCallThrough();
$controller('addGatewayCtrl', {
'$scope': $scope,
'$location': $location,
'$filter':$filter,
'sessionService': mockSessionService
});
}));
describe('call session service to get place data ', function () {
//resolve our mock place and session services
beforeEach(function () {
//resolve mocks
sessionDeferred.resolve(mockPlaces);
$rootScope.$apply();
});
//run tests
it('should have called sessionService get places', function () {
expect(mockSessionService.get).toHaveBeenCalledWith("places");
});
it('should have called sessionService get currentPlaceId', function () {
expect(mockSessionService.getCurrentPlace).toHaveBeenCalled();
});
it('should have set scope', function () {
expect($scope.place).toEqual(mockPlaces[0]);
});
});
});
So I figured it out. With nested deferred's you have to call $scope.$apply() in between. The following fixed it up (along with a few minor changes to the mock data responses, but those were trivial):
//resolve promises
activityMessagesDeferred.resolve(mockActivityMessages);
$rootScope.$apply();
$rootScope.$broadcast("draco.sessionRefreshed");
activityCountDeferred.resolve(mockActivityCount);
$rootScope.$apply();
placesDeferred.resolve(mockPlaces);
activityListDeferred.resolve(mockActivities);
$rootScope.$apply();

Resources