Madhan Ganesh
Principles
Predictable
Single Responsibility
Self Documenting
Tests Behaviour
Example to discuss
describe('add 2 numbers', function() {
it('should return sum of both operands', function() {
expect(calculator.add(1, 1)).to.be(2);
});
});
Predictable
One thing at a time
Self documenting
Test behaviour
Excuses
It is working, why to do more?
It will take too long and I need to know more!
My code just works fine
Unit test do not catch any bugs
Who cares? no business value
Oh! takes time to run them
If writing unit test is harder, listen carefully to your code...
Tightly couples components
Singletons
Mixed Concerns
Asynchronous functions
Karma
Jasmine
Sinon
Chai
Mocha
Protactor
Grunt
ngMock
A test double is an object that acts and is used in place of another object.
Test double
Object under test
// Arrange
jasmine.spyOn(calculator, 'add');
// Act
calculator.add(2, 3);
calculator.add(3, 4);
// Assert
assert(calculator.add.count).is(2);
var calculator = {
add: function(a, b) {
var result = a + b;
return result;
}
};
// Arrange
jasmine.spyOn(wordSource, 'getWord')
.and
.returnValue('simple');
// Act
var ret = wordManager.captilize();
// Assert
expect(ret).toBe('SIMPLE');
var wordManager = {
capitalize: function(wordSource) {
var word = wordSource.getWord(); // MY DEPENDENCY
return word.toUpperCase(); // MY LOGIC
}
};
// Arrange
wordManager.setPrefix('Mr.');
jasmine.spyOn(wordSource, 'getWord');
// Act
var ret = wordManager.captilize();
// Assert
var callArgs = wordSource.getWord.call.argsFor(0);
expect(callArgs).toBe('Mr.');
var wordManager = {
// .. logic to set 'myprefix'
capitalize: function(wordSource) {
var word = wordSource.getWord('myprefix '); // MY DEPENDENCY
return word.toUpperCase(); // MY LOGIC
}
};
var Calculator =
function(displayElement) {
this.el = $(displayElement);
};
Calculator.proptotype.hideResult =
function(callback) {
this.$el
.fadeOut(1000, callback);
}
describe('Calculator', function() {
var calc, el;
beforEach(function() {
el = $('<div>some content</div>');
calc = new Calculator(el);
});
it('should hide the result', function() {
// Arrange
var flag = false;
var callback = function() {
flag = true;
});
// Act
runs(function() {
calc.hideResult(callback);
});
waitsFor(function() {
return flag;
}, 'flag to be true', 1100);
// Assert
runs(function() {
expect(el.css('display')).toBe('none');
});
});
});
function hideAfterSometime() {
setTimeout(function() {
hide = true;
}, 1000);
}
beforeEach(function() {
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('should hide after 1 second', function() {
// Act
hideAfterSometime();
jasmine.clock().tick(1001);
// Assert
expect(hide).toBe(true);
});
function readFile(fileName, onsuccess) {
var f = new FileReader(fileName);
var data = f.readFile();
onsuccess(data);
}
Spy Function
it('should invoke callback after file is read', function() {
// Arrange
var spyCallback = jasmine.createSpy('successcb');
// Act
readFile('some.txt', spyCallback);
// Assert
expect(spyCallback).toHaveBeenCalled();
});
function WorkitemsManager(worklistService) {
this.exams = [];
this.readExams = function() {
this.exams = worklistService.getExams();
}
}
Spy Method - mock return
it('should invoke callback after file is read', function() {
// Arrange
var exams = ['abc'];
var worklistService = new WorklistService();
spyOn(worklistService, 'getExams').andReturn(exams);
// Act
var workitemsManager = new WorkitemsManager(worklistService);
workitemManager.readExams();
// Assert
expect(workitemsManager.exams).toEqual(exams);
});
function WorkitemsManager(worklistService) {
this.exams = [];
this.readExams = function() {
this.exams = worklistService.getExams();
}
}
Spy Method - and call through
it('should invoke callback after file is read', function() {
// Arrange
var worklistService = new WorklistService();
var spy = spyOn(worklistService, 'getExams').andCallThrough();
// Act
var workitemsManager = new WorkitemsManager(worklistService);
workitemManager.readExams();
// Assert
expect(spy).toHaveBeenCalled();
});
function WorkitemsManager(worklistService) {
this.exams = [];
this.readExams = function() {
try {
this.exams = worklistService.getExams();
} catch(ex) {
this.exams = [];
}
}
}
Spy Method - and throw
it('should invoke callback after file is read', function() {
// Arrange
var worklistService = new WorklistService();
spyOn(worklistService, 'getExams').andThrow(new Error('problem'));
// Act
var workitemsManager = new WorkitemsManager(worklistService);
workitemManager.readExams();
// Assert
expect(workitemManager.exams).toBe([]);
});
module.directive('spinner', function (spinnerSvc) {
return {
link: function (scope, element) {
scope.$on('showspinner', function () {
spinnerSvc.spin(element[0]);
});
scope.$on('stopspinner', function () {
spinnerSvc.stop(element[0]);
});
}
};
});
Spy Object
it('should show spinner on "showspinner" event', function () {
// Arrange
spinnerSvcMock = jasmine.createSpyObj('spinnerSvc', ['spin', 'stop']);
// Act
scope.$broadcast('showspinner');
// Assert
expect(spinnerSvcMock.spin).toHaveBeenCalledWith(element[0]);
});
function WorkitemsManager(worklistService) {
this.exams = [];
this.readExams = function() {
worklistService.getExams(function(exams) {
this.exams = exams;
});
}
}
Spy Method - mock callback
it('should invoke callback after file is read', function() {
// Arrange
var exams = ['abc'];
var worklistService = new WorklistService();
spyOn(worklistService, 'getExams').andCallFake(function(callback) {
callback(exams);
});
// Act
var workitemsManager = new WorkitemsManager(worklistService);
workitemManager.readExams();
// Assert
expect(workitemsManager.exams).toEqual(exams);
});
Setup
describe('HeaderContoller', function () {
beforeEach(module('app'));
beforeEach(module(function($provide) {
// inject mock services
}));
beforeEach(inject(function ($rootScope, $controller) {
// create scope, controllers and mocks
});
it('should test something', function() {
// Arrange
// Act
// Assert
});
...
});
Start here
Load Module
Customize DI
Mock objects
Tests..
(function () {
'use strict';
describe('Header Controller', function () {
var rootScope,
controllerFactory,
controller,
scope;
beforeEach(module('app'));
beforeEach(inject(function ($rootScope, $controller) {
rootScope = $rootScope;
scope = $rootScope.$new();
controllerFactory = $controller;
controller = controllerFactory('HeaderCtrl', {
$scope: scope
});
}));
it('it should ', function () {
});
});
})();
Testing Controller
(function() {
describe('worklist service', function () {
var worklistSvc;
beforeEach(module('app'));
beforeEach(inject(function (_worklistSvc_) {
worklistSvc = _worklistSvc_;
}));
it('', function() {
});
});
})()
Testing Service
(function() {
'use strict';
describe('Datepicker Directive', function() {
var element, scope, compile;
// setup - before each
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element('{{!cursor}}');
$compile(element)(scope);
scope.$digest();
compile = $compile;
}));
// Tests start here
});
})();
Testing Directive