<!DOCTYPE html>
<html>
<head>
<!-- jasmine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js"></script>
<!-- jasmine's html reporting code and css -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css" rel="stylesheet" />
<!-- angular itself -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js"></script>
<!-- angular's testing helpers -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular-mocks.js"></script>
<script src="myApp.js"></script>
<script src="myHttpController.js"></script>
<script src="mySvcApp.js"></script>
<script src="test/myMathSvcSpec.js"></script>
<script src="test/myMathControllerSpec.js"></script>
<script src="test/myThisControllerSpec.js"></script>
<script src="test/myHttpControllerSpec.js"></script>
<script src="test/myMathSvcSpec.js"></script>
<style>
.jasmine_reporter > .results > .summary >
.suite.passed > .description {
background-color: #a6b779;
padding: 3px;
color: #fff;
}
</style>
</head>
<body>
<h1>Unit Testing AngularJS with Jasmine</h1>
<h5> - By Paritosh!</h5>
<a taget="_blank" href="https://simplifyingtechblog.wordpress.com/2017/06/13/unit-testing-angularjs-with-jasmine/">
Open the blog
</a>
<hr/>
<!-- bootstrap jasmine! -->
<script>
var jasmineEnv = jasmine.getEnv();
// Tell it to add an Html Reporter
// this will add detailed HTML-formatted results
// for each spec ran.
jasmineEnv.addReporter(new jasmine.HtmlReporter());
// Execute the tests!
jasmineEnv.execute();
</script>
</body>
</html>
var mySvcApp = angular.module('mySvcApp', []);
mySvcApp.service('myHttpSvc', ['$http', function($http){
this.getServerData = function(){
return $http.get('myData.json');
};
this.addData = function(name){
return $http.post('postMethod', { 'name': name }, {});
};
}]);
mySvcApp.service('mySharedDataSvc', [function(){
var myProperty;
this.setMyProperty = function(value){
this.myProperty = value;
};
this.getMyProperty = function(value){
return this.myProperty;
};
}]);
mySvcApp.service('myMathSvc', [function(){
this.add = function(a,b){
if(isNaN(a) || isNaN(b)){
throw new Error('invalid parameters');
}
return a+b;
}
this.divide = function(a,b){
if(isNaN(a) || isNaN(b)){
throw new Error('invalid parameters');
}
if(b == 0){
throw new Error('divide by zero error');
}
return a/b;
}
}]);
var myApp = angular.module('myApp', ['mySvcApp']);
myApp.controller('myMathController',
['$scope', '$window', '$log', 'myMathSvc',
function($scope, $window, $log, myMathSvc){
$scope.param1 = 0;
$scope.param2 = 0;
$scope.result = 0;
$scope.addition = function(){
$scope.result = myMathSvc.add($scope.param1, $scope.param2);
};
$scope.division = function(){
try{
$scope.result = myMathSvc.divide($scope.param1, $scope.param2);
}
catch(e){
$window.alert(e.message);
$log.log(e);
$scope.result = '';
}
};
}]
);
myApp.controller('myThisController',
['$scope', '$timeout', 'myMathSvc',
function($scope, $timeout, myMathSvc){
this.param1 = 0;
this.param2 = 0;
this.result = 0;
this.addition = function(){
this.result = myMathSvc.add(this.param1, this.param2);
};
this.delayedAddition = function(){
var tempResult = myMathSvc.add(this.param1, this.param2);
$timeout(function(){
this.result = tempResult;
});
};
}]
);
describe('testing myMathController', function(){
var $controller, scope;
var ctrl, getController;
var windowMock, logMock, myMathSvc;
beforeEach(function(){
// Step 1. load the modules we need
module('mySvcApp');
module('myApp');
inject(function($injector){
$controller = $injector.get('$controller');
// Step 2. load/create external dependencies to inject
scope = $injector.get('$rootScope').$new();
myMathSvc = $injector.get('myMathSvc');
windowMock = {
alert: function(msg){}
};
logMock = {
log: function(msg){}
};
// Step 3. inject the dependencies
getController = function(){
return $controller('myMathController', {
'$scope': scope,
'$window': windowMock,
'$log': logMock,
'myMathSvc': myMathSvc
});
};
ctrl = getController();
});
});
it('1. should load controller properly', function(){
expect(ctrl).not.toBeNull();
expect(ctrl).not.toBe(undefined);
expect(scope.param1).toBe(0);
expect(scope.param2).toBe(0);
});
it('2. should call math service to add operands user has provided', function(){
// Step 4. prepare the data / set spies
var op1 = 5, op2 = 2, fakeResult = 7;
spyOn(myMathSvc, 'add').andReturn(fakeResult);
scope.param1 = op1;
scope.param2 = op2;
scope.addition();
scope.$digest();
// Step 5. now test the result we are expecting
expect(myMathSvc.add).toHaveBeenCalled();
expect(myMathSvc.add).toHaveBeenCalledWith(scope.param1, scope.param2);
expect(scope.result).toBe(fakeResult);
//other way to get call details
expect(myMathSvc.add.calls.length).toBe(1);
expect(myMathSvc.add.calls[0].args[0]).toBe(scope.param1);
expect(myMathSvc.add.calls[0].args[1]).toBe(scope.param2);
expect(myMathSvc.add.mostRecentCall.args[0]).toBe(scope.param1);
expect(myMathSvc.add.mostRecentCall.args[1]).toBe(scope.param2);
//ok, call it once more - want to see few more stuff
scope.param1 = 123;
scope.param2 = 234;
scope.addition();
scope.$digest();
expect(myMathSvc.add.mostRecentCall.args[0]).toBe(123);
expect(myMathSvc.add.mostRecentCall.args[1]).toBe(234);
//do you want to change the fake call result to anything else??
//no problem. We have got it covered
myMathSvc.add.isSpy = false;
spyOn(myMathSvc, 'add').andReturn(5000);
expect(myMathSvc.add.calls.length).toBe(0);
scope.addition();
scope.$digest();
expect(myMathSvc.add).toHaveBeenCalled();
expect(myMathSvc.add.calls.length).toBe(1);
expect(scope.result).toBe(5000);
});
describe('testing division operation', function(){
it('3. should call math service to divide operands use has provided', function(){
var op1 = 5, op2 = 2, fakeResult = 7;
spyOn(myMathSvc, 'divide').andReturn(fakeResult);
scope.param1 = op1;
scope.param2 = op2;
scope.division();
scope.$digest();
expect(myMathSvc.divide).toHaveBeenCalledWith(scope.param1, scope.param2);
expect(scope.result).toBe(fakeResult);
});
it('4. should alert/log if operands are not provided properly', function(){
var errorMsg = 'Fake Error Message';
spyOn(myMathSvc, 'divide').andThrow(new Error(errorMsg));
spyOn(windowMock, 'alert');
spyOn(logMock, 'log');
scope.param1 = 10;
scope.param2 = 'abc';
scope.division();
scope.$digest();
expect(myMathSvc.divide).toHaveBeenCalledWith(scope.param1, scope.param2);
expect(windowMock.alert).toHaveBeenCalledWith(errorMsg);
expect(logMock.log).toHaveBeenCalled();
});
});
});
describe('testing myHttpController', function(){
var $controller, scope, $compile;
var ctrl, getController;
var windowMock, logMock, myHttpSvc;
beforeEach(function(){
module('mySvcApp');
module('myApp');
inject(function($injector){
$compile = $injector.get('$compile');
$controller = $injector.get('$controller');
scope = $injector.get('$rootScope').$new();
myHttpSvc = $injector.get('myHttpSvc');
logMock = {
log: function(msg){}
};
getController = function(){
return $controller('myHttpController', {
'$scope': scope,
'$log': logMock,
'myHttpSvc': myHttpSvc
});
};
ctrl = getController();
});
});
it('1. should load controller properly', function(){
expect(ctrl).not.toBeNull();
expect(ctrl).not.toBe(undefined);
expect(angular.equals(scope.myData, [])).toBe(true);
});
it('2. should call getServerData and update scope.myData when scope.getData is called', function(){
var fakeResult = ['one', 'two', 'three'];
spyOn(myHttpSvc, 'getServerData').andCallFake(function(){
return {
then: function(callback){ callback(fakeResult); return this; },
finally: function(callback){ return callback(this); }
};
});
//// understand why we need to return the object 'this' in 'then' part.
//// as you can see that the same object is needed to call 'finally' (in the controller)
//// in the controller, if we had the async function called without finally,
//// we would have set the spy as per below
//spyOn(myHttpSvc, 'getServerData').andCallFake(function(){
// return {
// then: function(callback){ return callback(fakeResult); }
// };
//});
scope.getData();
scope.$digest();
expect(myHttpSvc.getServerData).toHaveBeenCalled();
expect(angular.equals(scope.myData, fakeResult)).toBe(true);
});
it('3. should call getData when view is loaded', function(){
var fakeResult = ['one', 'two', 'three'];
spyOn(scope, 'getData');
spyOn(myHttpSvc, 'getServerData').andCallFake(function(){
return {
then: function(callback){ callback(fakeResult); return this; },
finally: function(callback){ return callback(this); }
};
});
var viewTemplate = '<div ng-controller="myHttpController"></div>';
$compile(viewTemplate)(scope);
scope.$digest();
expect(scope.getData);
});
it('4. should do logging when name property gets changed - $watch', function(){
expect(scope.name).toBe('');
spyOn(logMock, 'log');
scope.name = 'Updated name';
scope.$digest();
expect(logMock.log).toHaveBeenCalledWith('Updated name');
});
});
describe('testing myMathSvc', function(){
var svcInstance;
beforeEach(function(){
module('mySvcApp');
// do injection in the argument of this 'function'
inject(function($injector){
svcInstance = $injector.get('myMathSvc');
});
});
it('1. should add two values', function(){
var x = 5, y = 7;
var result = svcInstance.add(x, y);
expect(result).toEqual(x+y);
});
it('2. should throw error when any of the operands isNaN', function(){
var x = 5, y = 'FOO';
expect(function(){ svcInstance.add(x, y)}).toThrow(new Error('invalid parameters'));
});
describe('testing divide method', function(){
it('3. should throw error if operators are isNaN', function(){
//ref: https://stackoverflow.com/a/27876020
expect(function(){ svcInstance.divide('foo', 'bar')}).toThrow(new Error('invalid parameters'));
expect(function(){ svcInstance.divide('foo', 5)}).toThrow(new Error('invalid parameters'));
expect(function(){ svcInstance.divide(5, 'bar')}).toThrow(new Error('invalid parameters'));
});
it('4. should throw error if divisor is zero', function(){
expect(function(){ svcInstance.divide(5, 0)}).toThrow(new Error('divide by zero error'));
});
it('5. should give me proper result when both operands are proper', function(){
var op1 = 10, op2 = 2;
var result = svcInstance.divide(op1, op2);
expect(result).toEqual(5);
});
});
});
describe('testing myThisController', function(){
var $controller, $timeout, scope;
var ctrl, getController;
beforeEach(function(){
module('mySvcApp');
module('myApp');
inject(function($injector){
$controller = $injector.get('$controller');
scope = $injector.get('$rootScope').$new();
myMathSvc = $injector.get('myMathSvc');
$timeout = $injector.get('$timeout');
getController = function(){
return $controller('myThisController', {
'$scope': scope,
'myMathSvc': myMathSvc,
'$timeout': $timeout
});
};
ctrl = getController();
});
});
it('1. should load controller properly', function(){
expect(ctrl).not.toBeNull();
expect(ctrl).not.toBe(undefined);
expect(ctrl.param1).toBe(0);
expect(ctrl.param2).toBe(0);
});
it('2. should call math service to add operands user has provided', function(){
var op1 = 5, op2 = 2, fakeResult = 7;
spyOn(myMathSvc, 'add').andReturn(fakeResult);
ctrl.param1 = op1;
ctrl.param2 = op2;
ctrl.addition();
scope.$digest();
expect(myMathSvc.add).toHaveBeenCalled();
expect(myMathSvc.add).toHaveBeenCalledWith(ctrl.param1, ctrl.param2);
expect(ctrl.result).toBe(fakeResult);
});
/*it('3. testing timeout', function(){
var op1 = 5, op2 = 2, fakeResult = 7;
spyOn(myMathSvc, 'add').andReturn(fakeResult);
ctrl.param1 = op1;
ctrl.param2 = op2;
ctrl.delayedAddition();
$timeout.flush();
scope.$digest();
expect(myMathSvc.add).toHaveBeenCalled();
expect(myMathSvc.add).toHaveBeenCalledWith(ctrl.param1, ctrl.param2);
expect(ctrl.result).toBe(fakeResult);
$timeout.verifyNoPendingTasks();
});*/
});
var myApp = angular.module('myApp');
myApp.controller('myHttpController',
['$scope', 'myHttpSvc', '$log',
function($scope, myHttpSvc, $log){
// add plunk in the blog - as inline plunk in the blog
$scope.myData = [];
$scope.showLoader = false;
$scope.name = '';
$scope.getData = function(){
$scope.showLoader = true;
myHttpSvc.getServerData().then(function(result){
$scope.myData = result;
}, errorCallback).finally(function(){
$scope.showLoader = false;
});
};
$scope.addData = function(){
$scope.showLoader = true;
myHttpSvc.addData($scope.name).then(function(result){
if(result == true){
$scope.name = '';
$scope.getData();
} else{
errorCallback(result);
}
}, errorCallback).finally(function(){
$scope.showLoader = false;
});
};
$scope.$on('$viewContentLoaded', function(){
$scope.getData();
});
$scope.$watch('name', function(newVal, oldVal){
$log.log(newVal);
})
function errorCallback(errorData){
$log.log(errorData);
}
}]
);