I have been searching through the internet for hours but didn't figure out how to fix my issue.
Here is my issue:
I'm unit-testing my controller with some calls to other services but in my controller I have a private function:
function showFileinAppB(pathFile, id) {
cordova.InAppBrowser.open(pathFile, '', '');
if (ctrl.isOnline) {
....
} else {
ApiService.countViewFile(id);
}
}
I'm stuck right at this line of code since it's calling to the native app cordova. I couldn't inject it to the test.
cordova.InAppBrowser.open(pathFile, '', '');
So my question is how to make my test ignore that line of code or is there any better ways to deal with private functions without changing code structure?
Edit: This is how I set up the unit test
describe('MyController', function() {
beforeEach(module('IonicApp'));
var $controller,
$scope,
$rootScope,
$q,
ApiService;
var cordova;
var fake = function() {
return true;
};
var fakePromise = function() {
var deferred = $q.defer();
deferred.resolve('Result');
return deferred.promise;
};
beforeEach(inject(function(_$controller_, $rootScope, _$q_, _ApiService_) {
$scope = $rootScope.$new();
$controller = _$controller_('MyController', {
$scope: $scope
});
$q = _$q_;
ApiService = _ApiService_;
spyOn(ApiService, 'countViewFile').and.callFake(fake);
cordova = {
InAppBrowser: {
open: function(fd, aa, bb) {
}
}
};
}));
it('Should be available', function() {
expect($controller).toBeDefined();
});
describe('MyController.onViewOnline', function() {
it('Should be available and call services', function() {
cordova.InAppBrowser.open = function(fd, aa, bb) {
return true;
};
expect($controller.onViewOnline).toBeDefined();
$controller.onViewOnline('item');
});
})
})
No you can't do that and that is not even advised.
And it's not possible to test private functions, one solution is that if that function is being called from any other function then you can test results over there.
Related
I have a simple enough function that closes an $mdSidenav instance in my application
function closeSideNav() {
$mdSidenav('left').close();
}
I'm now needing to unit test this, but am having trouble writing an expectation for the close() call on $mdSidenav.
I thought about using $provide in my test spec
module(function($provide) {
$provide.value('$mdSidenav', function(id) {
return {
close: jasmine.createSpy('$mdSidenav.close')
}
})
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
spyOn($mdSidenav, 'close');
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect($mdSidenav.close).toHaveBeenCalled();
});
});
This throws a couple of errors:
Error: close() method does not exist
Error: Expected a spy, but got undefined.
Has anyone managed to mock out $mdSidenav and offer me some guidance please?
Thanks
UPDATE
Based on the suggested answer, I have now updated my test spec to
'use strict';
describe('NavbarController', function() {
var $controller,
vm,
$mdSidenav,
sideNavCloseMock;
beforeEach(function() {
module('app.layout');
sideNavCloseMock = jasmine.createSpy();
module(function($provide) {
$provide.value('$mdSidenav', function() {
return function(sideNavId) {
return {close: sideNavCloseMock}
}
})
});
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
});
});
And for a sanity check, my actual controller looks as follows:
(function () {
'use strict';
angular
.module('app.layout')
.controller('NavbarController', Controller);
Controller.$inject = ['$mdSidenav'];
function Controller($mdSidenav) {
var vm = this;
vm.closeSideNav = closeSideNav;
//This only affects the sideNav when its not locked into position, so only on small\medium screens
function closeSideNav() {
$mdSidenav('left').close();
}
}
})();
Unfortunately this still isn't working for me, and I end up with a different error
TypeError: undefined is not a constructor (evaluating '$mdSidenav('left').close())
close method doesn't belong to $mdSidenav. $mdSidenav is a function that returns a side nav object. That's why it complains 'close() method does not exist'.
What you can do is mock the $mdSidenav to return an object hat has mocked close method, like this: -
var sideNavCloseMock;
beforeEach(module(function($provide){
sideNavCloseMock = jasmine.createSpy();
$provide.factory('$mdSidenav', function() {
return function(sideNavId){
return {close: sideNavCloseMock};
};
});
}));
then do
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
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.
I have a service that i wrote for a project i am currently working on and i am trying to write a unit test for it. Below is the code for the service
angular.module('services').factory('Authorization', ['$q', 'Refs', function($q, Refs) {
function isAuthorized() {
var deferred = $q.defer();
var authData = Refs.rootRef.getAuth();
var adminRef;
if(authData.google) {
adminRef = Refs.rootRef.child('admins').child(authData.uid);
} else {
adminRef = Refs.rootRef.child('admins').child(authData.auth.uid);
}
adminRef.on('value', function(adminSnap) {
deferred.resolve(adminSnap.val());
});
return deferred.promise;
}
return {
isAuthorized: isAuthorized
};
}]);
I have written a unit test for it but anytime i run the test i get this error message ' Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'
Below is the code for the unit test i wrote for the service:
'use strict';
describe('Authorization Service', function() {
var Refs, $q, Authorization, authData, scope, deferred;
beforeEach(angular.mock.module('Sidetime'));
beforeEach(inject(function(_Refs_, $rootScope, _$q_, _Authorization_) {
Refs = _Refs_;
$q = _$q_;
Authorization = _Authorization_;
}));
iit('return admin object', function(done) {
var result;
Authorization.isAuthorized = function() {
deferred = $q.defer();
authData = {google: 'uid:112222'};
if(authData.google) {
deferred.resolve(authData);
}
return deferred.promise;
};
Authorization.isAuthorized().then(function(result) {
console.log('result', result);
expect(result).toEqual(authData);
//done();
});
});
});
I am not sure I am writing the unit test properly. I will appreciate if someone could show be a better way of writing the unit test for this service. Thanks for your anticipated assistance.
Here is a working plunkr, with slight modifications as I don't have the code to all of your dependencies:
angular.module('services', []).factory('Authorization', ['$q', function($q) {
function isAuthorized() {
var deferred = $q.defer();
deferred.resolve({authData: {google: 'uid: 1122222'}});
return deferred.promise;
}
return {
isAuthorized: isAuthorized
};
}]);
describe('Authorization Service', function() {
var $q, Authorization, scope, deferred;
beforeEach(angular.mock.module('services'));
beforeEach(inject(function($rootScope, _$q_, _Authorization_) {
$q = _$q_;
scope = $rootScope.$new();
Authorization = _Authorization_;
Authorization.isAuthorized = function() {
deferred = $q.defer();
authData = {google: 'uid:112222'};
if(authData.google) {
deferred.resolve(authData);
}
return deferred.promise;
};
}));
it('return admin object', function(done) {
var result;
var promise = Authorization.isAuthorized();
promise.then(function(result) {
expect(result).toEqual(authData);
expect(result.google).toEqual('uid:112222e');
});
deferred.resolve(result);
scope.$digest();
});
});
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
/**
Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
*/
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
/**
Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
*/
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
/**
Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler
### Test Results
Scroll down to see the results of all of these specs.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
//document.querySelector('.version').innerHTML = jasmineEnv.versionString();
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
http://plnkr.co/edit/DCrr6pVzF9D4OuayCZU7?p=preview
Take a look over at spies in jasmine to get a deeper introduction
I've taken Cognitroics plunkr and modified it in the way I usually write my test. Take a look at it here http://plnkr.co/edit/W5pP82CKj7tc6IO3Wj9S?p=preview
But basically what you should do is utilizing the awesomeness of spies in jasmine.
Here's how it looks like.
describe('My aweomse test suite', function(){
beforeEach(function(){
module('Awesome');
inject(function($injector){
this.MyService = $injector.get('MyService');
this.$q = $injector.get('$q');
this.$scope = $injector.get('$rootScope').$new();
spyOn(this.MyService, 'someMethod').andCallFake(function(){
var defer = this.$q.defer();
defer.resolve();
return defer.promise;
});
});
it('should do seomthing', function(){
// given
var result;
// when
this.MyServcie.someMethod().then(function(){
result = 'as promised';
});
this.$scope.$digest();
// then
expect(result).toEqual('as promised');
});
});
});
I am a Jasmine rookie and trying to figure out how to mock the window.user.username object when testing an angularjs using Jasmine.
var applicationUser = window.user.username;
I just figured out, Not Sure, if this is the right way. But it worked. Hope it helps.
//Main Function
function MyController($scope, $window) {
$scope.HeaderCtrl = function() {
$scope.user = $window.user;
---
}
//Jasmine Code
var $window;
beforeEach(inject(function(_$window_) {
$window = _$window_;
$window.user = 'xyz';
}));
createController = function() {
return $controller('EpicController', {
'$scope': $rootScope,
'$window': $window
});
};
Another way of doing it,
Mocking window in an angularJS test
you have to add user object to jasmine global space.
//Main Function
function MyController($scope, $window) {
$scope.HeaderCtrl = function() {
$scope.user = $window.user;
---
}
//Jasmine Code
var $window;
beforeEach(inject(function(_$window_) {
jasmine.getGlobal().$window = _$window_;
// OR
// here i am assuming that user class has only username property
jasmine.getGlobal().$windows = { "user": {"username": "xyz"}};
$window.user = 'xyz';
}));
createController = function() {
return $controller('EpicController', {
'$scope': $rootScope,
'$window': $window
});
};
I have a controller that is calling a service. I want to write my unit tests such that I get coverage on the success and error functions of the then function.
maApp.controller('editEmailAndPasswordController',
["$scope", "emailAndPasswordService",
function editEmailAndPasswordController($scope, emailAndPasswordService) {
$scope.EmailId = 'as#as.com';
$scope.CurrentPassword = '';
$scope.Success = false;
$scope.save = function () {
var request = {
currentPassword: $scope.CurrentPassword,
newEmailId: $scope.EmailId
};
emailAndPasswordService.save(request).then(function (data) {
$scope.Success = true;
}, function (data, status, header, config) {
$scope.Success = false;
});
};
}]);
This is what I have got so for. I want another test for the fail condition as well, but not sure how to set up the mock service.
describe('Controllers', function () {
var $scope, ctrl, controller, svc, def;
describe('editEmailAndPasswordController', function () {
beforeEach(function() {
module('maApp');
});
beforeEach(inject(function ($controller, $rootScope, $q) {
ctrl = $controller;
svc = {
save: function () {
def = $q.defer();
return def.promise;
}
};
spyOn(svc, 'save').andCallThrough();
$scope = $rootScope.$new();
controller = ctrl('editEmailAndPasswordController', { $scope: $scope, emailAndPasswordService: svc });
}));
it('should set ShowEdit as false upon save', function () {
$scope.ShowEdit = true;
$scope.EmailId = 'newEmail';
$scope.CurrentPassword = 'asdf1';
$scope.save();
expect($scope.EmailId).toBe('as#as.com');
expect($scope.Success).toBe(true);
});
});
});
You have some real problems with this code.
Don't call ".andCallThrough()"- that way your test depends on the implementaton of the service and means your controller is not isolated. The main idea is to create unit tests.
svc = {save: jasmine.createSpy()};
svc.save.andReturn(...);
You can't assert against expect($scope.EmailId).toBe('as#as.com'); because you change the value in the code to $scope.EmailId = 'newEmail';
you can create 2 private methods for readability
function success(value) {
var defer = q.defer();
defer.resolve(value);
return defer.promise;
}
function failure(value){
var defer = q.defer();
defer.reject(value);
return defer.promise;
}
Thus in the first test you can call
svc.save.andReturn(success());
$scope.$digest()
expect($scope.Success).toBeTruthy();
And in the other test you will have the same but:
svc.save.andReturn(failure());
$scope.$digest()
expect($scope.Success).toBeFalsy();
In one case, you want the promise to be successful, so you want to resolve the deferred:
$scope.save();
def.resolve('whatever');
$scope.$apply();
expect($scope.Success).toBe(true);
...
In the other case, you want the promise to be a failure, so uou want to reject the deferred:
$scope.save();
def.reject('whatever');
$scope.$apply();
expect($scope.Success).toBe(false);
...
This is explained in the documentation.