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?
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
});
};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() {
});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() {
});idempotent - your test's shouldn't affect the next. Use teardown
stub/mock/spy - focus on testing ONE function and mocking dependencies.
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);
});
});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()
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
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');
});
});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);
});
});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>');
});
});