Getting started with Ionic/Angular unit testing controllers with jasmine - angularjs

I have been trying to write unit tests for angular controller in Ionic framework with jasmine and karma. Here is my controller.
angular.module('starter').controller('LoginCtrl', ['$scope', 'AuthService', '$ionicPopup', '$state', function LoginCtrl($scope, AuthService, $ionicPopup, $state) {
$scope.user = {
name: '',
password: ''
};
$scope.login = function() {
AuthService.login($scope.user).then(function(msg) {
$state.go('inside');
}, function(errMsg) {
var alertPopup = $ionicPopup.alert({
title: 'Login failed!',
template: errMsg
});
});
};
}]);
angular.module('starter').controller('RegisterCtrl', ['$scope', 'AuthService', '$ionicPopup', '$state', function RegisterCtrl($scope, AuthService, $ionicPopup, $state) {
$scope.user = {
name: '',
password: ''
};
$scope.signup = function() {
AuthService.register($scope.user).then(function(msg) {
$state.go('outside.login');
var alertPopup = $ionicPopup.alert({
title: 'Register success!',
template: msg
});
}, function(errMsg) {
var alertPopup = $ionicPopup.alert({
title: 'Register failed!',
template: errMsg
});
});
};
}]);
angular.module('starter').controller('InsideCtrl', ['$scope', 'AuthService', 'API_ENDPOINT', '$http', '$state', function InsideCtrl($scope, AuthService, API_ENDPOINT, $http, $state) {
$scope.destroySession = function() {
AuthService.logout();
};
$scope.getInfo = function() {
$http.get(API_ENDPOINT.url + '/memberinfo').then(function(result) {
$scope.memberinfo = result.data.msg;
});
};
$scope.logout = function() {
AuthService.logout();
$state.go('outside.login');
};
}]);
angular.module('starter').controller('AppCtrl', ['$scope', '$state', '$ionicPopup', 'AuthService', 'AUTH_EVENTS', function AppCtrl($scope, $state, $ionicPopup, AuthService, AUTH_EVENTS) {
$scope.$on(AUTH_EVENTS.notAuthenticated, function(event) {
AuthService.logout();
$state.go('outside.login');
var alertPopup = $ionicPopup.alert({
title: 'Session Lost!',
template: 'Sorry, You have to login again.'
});
});
}]);
})(reader || (reader = {}));
I want to have this 3 test suits:
Should call login on $AuthService Service
If login successful, should change state to inside
If login unsuccessful, should show a popup error message
I em try to write som test but i thin that i am not injecting the controller properly. Here is the code for my tests:
describe('LoginCtrl', function() {
var $controller,
LoginCtrl,
controller,
deferredLogin,
$scope,
AuthServiceMock,
stateMock,
ionicPopupMock;
// load the module for our app
beforeEach(angular.mock.module('starter'));
// disable template caching
beforeEach(angular.mock.module(function($provide, $urlRouterProvider) {
$provide.value('$ionicTemplateCache', function(){} );
$urlRouterProvider.deferIntercept();
}));
// instantiate the controller and mocks for every test
beforeEach(angular.mock.inject(function(_$controller_, $q, $rootScope) {
deferredLogin = $q.authToken();
$scope = $rootScope.$new();
// mock dinnerService
AuthServiceMock = {
login: jasmine.createSpy('login spy')
.and.returnValue(deferredLogin.promise)
};
// mock $state
stateMock = jasmine.createSpyObj('$state spy', ['go']);
// mock $ionicPopup
ionicPopupMock = jasmine.createSpyObj('$ionicPopup spy', ['alert']);
// instantiate LoginController
$controller = _$controller_;
}));
describe('login function', function() {
// call doLogin on the controller for every test
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
var user = {
name: 'Boris',
password: 'Boris'
};
$scope.login(user);
}));
it('should call login on $AuthService Service', function() {
LoginCtrl = $controller('LoginCtrl', {
'$scope': $scope,
'$state': stateMock,
'$ionicPopup': ionicPopupMock,
'$AuthService': AuthServiceMock
});
console.log(LoginCtrl);
expect(LoginCtrl).toBeDefined();
});
describe('when the login is executed,', function() {
xit('if successful, should change state to inside', function() {
deferredLogin.resolve();
$rootScope.$digest();
expect(stateMock.go).toHaveBeenCalledWith('inside');
});
xit('if unsuccessful, should show a popup', function() {
deferredLogin.reject();
$rootScope.$digest();
expect(ionicPopupMock.alert).toHaveBeenCalled();
});
});
});
});
Here is my Karma file settings
files: [
'www/lib/ionic/js/ionic.bundle.js',
'www/js/*.js',
'www/lib/angular-mocks/angular-mocks.js',
'www/spec/*.js'
],

Related

How to call function of one controller from another controller AngularJs

I have two controller name called like:
AutoLoginCtrl
LoginCtrl
my facebook logout function present in Login Controller, from AutoLogin controller how could I call logout function which is Login Controller.
I have tried like this by using $rootscope and $on
LoginCtrl.js
ionApp.controller('LoginCtrl', function($scope, $state, $ionicModal, $rootScope, $q, $timeout, UserService, $ionicLoading, $ionicActionSheet) {
$scope.showLogOutMenu = function() {
var hideSheet = $ionicActionSheet.show({
destructiveText: 'Logout',
titleText: 'Are you sure you want to logout? This app is awsome so I recommend you to stay.',
cancelText: 'Cancel',
cancel: function() {},
buttonClicked: function(index) {
return true;
},
destructiveButtonClicked: function() {
$ionicLoading.show({
template: 'Logging out...'
});
// Facebook logout
facebookConnectPlugin.logout(function() {
$ionicLoading.hide();
$state.go('login');
},
function(fail) {
$ionicLoading.hide();
});
}
});
};
$rootScope.$on("LogoutFunction", function(){
$scope.parentmethod();
});
})
AutoLoginCtrl
ionApp.controller('AutoLoginCtrl', function($scope, $state, $ionicModal, $rootScope, $q, $timeout, UserService, $ionicLoading, $ionicActionSheet) {
var checkPage = "home";
$scope.logout = function() {
$rootScope.$emit("LogoutFunction", {});
}
})
You decided to use $emit a.e. child controller notifies parent controller. Is it true? AutoLoginCtrl is a child of LoginCtrl or maybe you need to use $broadcast instead?
The second problem might be:
For some reason LoginCtrl still didn't load and no one listens on LogoutFunction event.
Please check these cases
you can make a Public Class (Provider Maybe) and make a function in it that call a function 'Logout()' from 'LoginController'
and call this provider function in Your 'AutoLogin' Controller
You can have the logout function in a factory and call it from both the controllers, like this.
Factory basic demo: here
Code:
ionApp.controller('LoginCtrl', function($scope, $ionicModal, $rootScope, $q, $timeout, UserService, SharedService) {
$scope.showLogOutMenu = function() {
SharedService.logout();
};
}).factory('SharedService', function ($ionicActionSheet, $ionicLoading, $state) {
return {
logout: function () {
var hideSheet = $ionicActionSheet.show({
destructiveText: 'Logout',
titleText: 'Are you sure you want to logout? This app is awsome so I recommend you to stay.',
cancelText: 'Cancel',
cancel: function() {},
buttonClicked: function(index) {
return true;
},
destructiveButtonClicked: function() {
$ionicLoading.show({
template: 'Logging out...'
});
// Facebook logout
facebookConnectPlugin.logout(function() {
$ionicLoading.hide();
$state.go('login');
},
function(fail) {
$ionicLoading.hide();
});
}
});
}
}
}).controller('AutoLoginCtrl', function($scope, $ionicModal, $rootScope, $q, $timeout, UserService, SharedService) {
var checkPage = "home";
$scope.logout = function() {
SharedService.logout();
}
});

Angular controller not injected in test

I have a simple login controller:
'use strict';
angular.module('login', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
}])
.controller('LoginCtrl', ["$scope", "$route", "LoginService", function ($scope, $route, LoginService) {
var self = this;
this.showGuestLogin = true;
this.showUserLogin = false;
this.toggleUserLoginType = function () {
this.showGuestLogin = !this.showGuestLogin;
this.showUserLogin = !this.showUserLogin;
}
this.submitGuestLogin = function()
{
if(this.guestName === undefined || this.guestName.trim() == '')
{
self.loginError = "Name cannot be blank";
return;
}
LoginService.loginAsGuest(this.guestName.trim())
.then(function()
{
self.loginError = null;
$route.reload();
})
.catch(function(err)
{
self.loginError = 'An error occured. Please try again';
});
}
}]);
I am trying to test it with:
describe('LoginCtrl', function()
{
beforeEach(module('login'));
var ctrl;
beforeEach(inject(function($controller)
{
ctrl = $controller('LoginCtrl');
}));
it('should set error if guest name is undefined', function(done)
{
ctrl.guestName = undefined;
ctrl.submitGuestLogin();
expect(ctrl.loginError).toBeDefined();
});
});
But I am getting this error in console when test runs
Error: [$injector:unpr]
http://errors.angularjs.org/1.5.8/$injector/unpr?p0=%24scopeProvider%20%3C-%20%24scope%20%3C-%20LoginCtrl
I can see in the developer console in the karma driven browser that the controller and it's dependant files are all being loaded correctly.
I can't see what is wrong?
UPDATE
I have tried the suggestions of passing an empty object:
beforeEach(inject(function($controller, $scope, $route, LoginService)
{
ctrl = $controller('LoginCtrl', {
});
}));
and setting up the dependencies:
beforeEach(inject(function($controller, $scope, $route, LoginService)
{
ctrl = $controller('LoginCtrl', {
$scope: $scope,
$route: $route,
LoginService: LoginService
});
}));
Both of which give me this error:
Error: [$injector:unpr]
http://errors.angularjs.org/1.5.8/$injector/unpr?p0=%24scopeProvider%20%3C-%20%24scope
It's because you need to add in the scope in the injection like this:
beforeEach(inject(function($controller, $scope) {
ctrl = $controller('LoginCtrl', { $scope: $scope });
}));
Similarly, if your real controller has injections that you will be using for testing, you'll need to add them in. So for example (and this is only an example):
ctrl = $controller('LoginCtrl',
{
$scope: $scope,
SomeService: SomeService,
moment: moment,
dateFormat: dateFormat
});
Found an answer here which worked: Angular Unit Test Unknown provider: $scopeProvider
beforeEach(inject(function($controller, $rootScope, $route, LoginService)
{
scope = $rootScope.$new();
ctrl = $controller('LoginCtrl', {
$scope: scope
});
}));
In my case I didn't actually need $scope injected into my controller, so I removed it an the original code now works:
beforeEach(inject(function($controller, $rootScope, $route, LoginService)
{
ctrl = $controller('LoginCtrl');
}));
I need to read up on how mocks and injection works!

Angular-Jasmine injecting a service into the test

New to Jasmine, I am trying to instantiate my controller which has a list of dependencies (mainly the services I have written) and all the different ways I';ve tried haven't been right.
Here is my controller:
(function () {
'use strict';
angular.module('app.match')
.controller('MatchController', MatchController);
MatchController.$inject = ['APP_CONFIG', '$authUser', '$http', '$rootScope', '$state', '$stateParams', 'SearchService', 'ConfirmMatchService', 'MusicOpsService', 'ContentOpsService', 'MatchstickService', 'MatchService', 'Restangular'];
function MatchController(APP_CONFIG, $authUser, $http, $rootScope, $state, $stateParams, searchService, confirmMatchService, musicOpsService, contentOpsService, matchstickService, matchService, Restangular) {
var vm = this;
vm.greeting = '';
.
.
)();
Here is my test
(function(){
'use strict';
describe('app module', function() {
var MatchController;
//beforeEach(module('app.match'));
beforeEach(function($provide) {
module = angular.module('app.match');
$provide.service('SearchService', function(){
});
});
beforeEach(module('app.config'));
beforeEach(module('auth'));
beforeEach(inject(function($controller, APP_CONFIG, $authUser, $http, $rootScope, $state, $stateParams) {
MatchController = $controller('MatchController', {'APP_CONFIG':APP_CONFIG, '$authUser':$authUser, '$http':$http, '$rootScope':$rootScope, '$state':$state, '$stateParams':$stateParams, '$provide':$provide});
}));
describe("Match controller", function() {
it("should be created successfully", function() {
expect(MatchController).toBeDefined();
});
});
});
})();
Running test the above way gives me the following error:
TypeError: 'undefined' is not a function (evaluating '$provide.service('SearchService', function(){
})')
Try injecting the SearchService like this instead of using beforeEach.
describe('app module', function() {
var MatchController, SearchService;
beforeEach(module('app.match'));
beforeEach(module('app.config'));
beforeEach(module('auth'));
beforeEach(inject(function($controller, APP_CONFIG, $authUser, $http, $rootScope, $state, $stateParams, _SearchService_) {
SearchService = _SearchService_;
MatchController = $controller('MatchController', {
'APP_CONFIG':APP_CONFIG,
'$authUser':$authUser,
'$http':$http,
'$rootScope':$rootScope,
'$state':$state,
'$stateParams':$stateParams,
'$provide':$provide,
'SearchService': _SearchService_
});
}));
describe("Match controller", function() {
it("should be created successfully", function() {
expect(MatchController).toBeDefined();
});
});
});
})();
Similarly, you'll have to inject other services as well that your controller is relying on.

AngularJs unit test: "Unsatisfied requests: DELETE"

Getting "Unsatisfied requests: DELETE" for my controller test. below are the code for my service, controller and test. tests are written using jasmine-karma and angular-mock. Thanks in advance.
service:
function deleteAsset(assetId) {
return $http.delete(baseUrl + '/delete/' + assetId).then(assetDeleted);
function assetDeleted(response) {
return response.data;
}
}
Controller:
(function () {
'use strict';
angular.module('tbApp').controller('assetEditCtrl', assetEditCtrl);
assetEditCtrl.inject = ['$routeParams', 'Campaign', 'User', '$location', 'Asset', '$window', '$scope', '$compile', 'ImageLibrary', 'Lists'];
function assetEditCtrl($routeParams, Asset, $window, $scope, $compile) {
function deleteAsset(assetId,assetType,assetName, el) {
//confirmation popup
CB.setProceedBoxCode("Are you sure you want to delete "+assetType+" "+assetName+"?", el, {OK: "Yes", CANCEL: "No", width: "400", title: "", callback: function (r, ele) {
if (r == true) {
Asset.deleteAsset(assetId).then(assetDeleted);
}
}});
function assetDeleted(data) {
backTodashbiard();
}
} } });
Test:
describe('assetEditCtrl', function() {
var scope, httpBackend, http, controller, vm, createController, assetId, el, assetType, assetName, compile;
var baseUrl = './webresources/assets';
beforeEach(module("tbApp"));
beforeEach(inject(function($rootScope, $httpBackend, $controller, $http, $compile) {
httpBackend = $httpBackend;
compile = $compile
scope = $rootScope.$new();
controller = $controller;
assetId= "EML1000006003";
campaignId="CMP1000004385";
createController = function() {
var controller = $controller('assetEditCtrl', {
'$scope': scope
});
return controller;
};
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
})
it('should be able to delete an asset', function(){
vm=createController();
assetType="email";
assetName="Untitled";
httpBackend.expectDELETE(baseUrl+'/delete/'+assetId).respond({});
vm.deleteAsset(assetId,assetType,assetName, el);
httpBackend.flush();
});});
Output:
Error: Unsatisfied requests: DELETE ./webresources/assets/delete/EML1000006003 in D:/Tb_test/src/main/webapp/js/libraries/uncompressed/angular-mocks.js
createHttpBackendMock/$httpBackend.verifyNoOutstandingExpectation#D:/Tb_test/src/main/webapp/js/libraries/uncompressed/angular-mocks.js:1488:7
createHttpBackendMock/$httpBackend.flush#D:/Tb_test/src/main/webapp/js/libraries/uncompressed/angular-mocks.js:1467:5
#D:/Tb_test/src/test/js/assetEditCtrlSpec.js:164:3
env.executeFiltered#D:/Tb_test/node_modules/karma-jasmine/lib/boot.js:117:7
createStartFn/<#D:/Tb_test/node_modules/karma-jasmine/lib/adapter.js:171:5
[2]</Karma/this.loaded#http://localhost:9876/karma.js:185:7

How can I test if a $cookie was set in an AngularJS service?

My service is:
myApp.service('userService', [
'$http', '$q', '$rootScope', '$state', '$cookies', '$base64', function($http, $q, $rootScope, $state, $cookies, $base64) {
var user;
user = {};
this.logout = function() {
user = {};
delete $cookies.userAccessKey;
return $state.transitionTo('login');
};
}
]);
I want to write a unit test to make sure that the $cookies.userAccessKey was deleted. How can I do this? While I'm at it, how can I ensure that user was set to empty?
To check if user is null, just add a public getter or checker :
myApp.service('userService', [
'$http', '$q', '$rootScope', '$state', '$cookies', '$base64', function($http, $q, $rootScope, $state, $cookies, $base64) {
var user;
this.user = {}; // or a method this.isLoggedIn() for more security
this.logout = function() {
this.user = {};
delete $cookies.userAccessKey;
return $state.transitionTo('login');
};
}
]);
Then, in your tests :
describe('logout', function(){
beforeEach(function(){
userService.login(); //relog before each test
});
it('should remove the userAccessKey from cookie', function() {
userService.logout();
inject(function($cookies) {
expect(cookies.userAccessKey).toBeUndefined();
});
});
it('should reset user object', function() {
userService.logout();
expect(userService.user).toEqual({});
});
});
});

Resources