Introduction
- Chester Rivas - chester.rivas@mobilelive.ca
- Unit Test Demo Repo - github.com/crivas/unit-test-demo
- Follow Along - slides.com/chesterrivas/unit-testing/live
Unit Testing
what is unit testing?
why should we unit test?
what tools do we use to unit test?
why do we need both karma and jasmine?


Karma Config
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: [
'jasmine',
'phantomjs-shim'
],
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],
// optionally, configure the reporter
coverageReporter: {
type: 'html',
dir: 'coverage'
},
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'app/views/**/*.html': 'ng-html2js',
'app/js/**/*.js': 'coverage'
},
junitReporter: {
outputFile: 'junit/test-results.xml',
suite: ''
},
// list of files / patterns to load in the browser
files: [
// bower library
'app/bower_components/angular/angular.js',
'app/bower_components/ui-router/release/angular-ui-router.js',
'app/bower_components/angular-mocks/angular-mocks.js',
// our app
'app/js/**/*.js',
// tests
'tests/unit-tests/**/*.js',
//location of templates
'app/views/**/*.html'
],
// list of files to exclude
exclude: [],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['PhantomJS'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
});
};Some Terms
suite - a javascript function containing more suits or specs, this is represented with describe() method, the signature looks like this:
describe('in this factory', function() {
});Some Terms
spec - has similar signature to describe, it is represented by it() method and this is where you will make your assertions. The goal is for the tests to be human readable: ie: "it should do something"
it('should return correct sum', function() {
});Some Terms
-
assertions - are functions provided by Jasmine that allow you to assert or assume a function will provide the expected results.
-
idempotent - your test's shouldn't affect the next. Use teardown
-
stub/mock/spy - focus on testing ONE function and mocking dependencies.
Jasmine Assertions
- toEqual() - only checks the value
- toBe() - compares objects, could be equal but not the same
- toBeDefined() - is not undefined
- toContain() - useful for searching within a string
- toHaveBeenCalled() - if has passed through the expected function
- not() - can be chained, will return you the opposite, ie: not.toBe()
Suite & Spec Example
var numberSquared = function(a) {
return a * a;
};
describe('math computations', function() {
it('should return the passed in number squared', function() {
expect(numberSquared(2)).toEqual(4);
expect(numberSquared(3)).toEqual(9);
expect(numberSquared(4)).toEqual(16);
});
});Some More Terms
focusing - you can focus a suite or a spec by putting f in front: fit() and fdescribe(), now when you run karma it will only test the focused suites and specs (used to be iit and ddescribe)
ignoring - the opposite of what focusing does, putting an x in font: xit() and xdescribe()
Some More Terms
afterEach - used to teardown your spec, so it doesn't effect the next test, will return literally after every spec
beforeEach - very frequently used, will run before every spec
What Should We Test?
- first test your services/factories/providers because these contain logical functions that can be easily tested and offer more value
- assuming you have most of your logic in services, then you shouldn't have to test your controllers, but if you don't then you should consider moving it to a service
- testing directives is tricky because you're not only testing javascript, you're also testing DOM manipulation associated with the directive
Factory Example
describe('mathFactory', function () {
var mathFactory;
beforeEach(module('demoApp'));
beforeEach(inject(function ($injector) {
mathFactory = $injector.get('mathFactory');
}));
it('should have convertMinutesToHours method defined', function () {
expect(mathFactory.convertMinutesToHours).toBeDefined();
});
it('should return hours formatted correctly', function () {
expect(mathFactory.convertMinutesToHours(1)).toBe('0:01');
expect(mathFactory.convertMinutesToHours(122)).toBe('2:02');
expect(mathFactory.convertMinutesToHours(489)).toBe('8:09');
});
});Controller Example
describe('homeCtrl', function () {
var $scope,
$controller;
beforeEach(module('demoApp'));
beforeEach(inject(function ($injector) {
$scope = $injector.get('$rootScope').$new();
$controller = $injector.get('$controller');
$controller('homeCtrl', { $scope: $scope });
}));
it('should have $scope variables defined', function () {
expect($scope.hello).toBeDefined();
expect($scope.items).toBeDefined();
expect($scope.addItem).toBeDefined();
});
it('should increase the length of items array when calling addItem', function () {
expect($scope.items.length).toEqual(0);
$scope.addItem('unit testing')
expect($scope.items.length).toEqual(1);
});
});Directive Example
describe('hello directive', function () {
var $rootScope, $compile, markup, element, $directiveScope;
beforeEach(module('demoApp'));
beforeEach(inject(function ($injector) {
$rootScope = $injector.get('$rootScope');
$rootScope.madeUpName = 'Ace Ventura';
$compile = $injector.get('$compile');
markup = '<hello-world name="{{madeUpName}}"></hello-world>';
element = $compile(markup)($rootScope);
$directiveScope = element.isolateScope();
$rootScope.$digest();
}));
it('should have proper dom structure with the correct name', function () {
// less specific
expect(element.html()).toContain('Ace Ventura')
// super specific
expect(element.html()).toEqual('<h3 class="ng-binding">Hello my name is Ace Ventura</h3>');
});
});Bad Practices
- be careful with false positives
- functions that make $http requests, all tests can NOT be async
- do not create large test suites, break it up into scenarios
- you don't have to run all the tests when developing, focus on the test you're working on (fit, fdescribe)
- don't try to get too fancy, don't try to run tests in a loop or test randomness
Good Practices
- make smoke screen tests that will run first, if those don't pass then there's no need to run anything else, this is the basic test that should always pass
- organize your tests into folders and name the folders in order you want them to run, i.e. smoke screen first
- code coverage is a guideline but not the law
- integrate your repo with your bamboo build, on every commit run unit test to give you the most up to date status
- do not push with focused tests
UTE-UI
- we currently have 110 tests
- roughly 50% coverage
- running on 3 browsers

Unit Testing
By Chester Rivas
Unit Testing
Presentation on unit testing in the AngularJS environment
- 1,579