AngularJS unit testing

html:

<!doctype html>
<html ng-app="app">
<head>
	<meta charset="utf-8">
	<title>AngularJS dev</title>
 
	<link href="bootstrap/css/bootstrap.css" rel="stylesheet">
	<link href="css/style.css" rel="stylesheet">
 
	<script src="js/vendor/angular.js"></script>
	<script src="js/app.js"></script>
	<script src="js/appCtrl.js"></script>
	<script src="js/appDrct.js"></script>
	<script src="js/appSrvc.js"></script>
	<script src="js/appFltr.js"></script>
</head>
 
<body>
<article ng-controller="appCtrl" class="container">
 
	<button ng-click="incrementCount()">Increment</button>
	count: {{count}}
 
	<div class-well>{{2 + 2}}</div>
 
	<h3>List of countries</h3>
	<ul>
		<li ng-repeat="country in srvc.countriesList">
			{{country.name}} - {{country.capital | reverse}}
		</li>
	</ul>
 
	<h3>Check if country is in the list</h3>
	<p>Country Name:
		<input type="text" ng-model="country.name" />
	</p>
	<p>
		<button ng-click="checkCountry()">Check country</button>
	</p>
 
</article>
</body>
</html>

AngularJS unit testing using Karma and Jasmine

AngularJS controller unit testing:

appCtrl.js:

app.controller('appCtrl', function ($scope, appSrvc) {
	$scope.count = 5;
	$scope.srvc = appSrvc; // bind scope variable to service
 
	$scope.incrementCount = function() {
		$scope.count = $scope.count + 1;
	};
 
	$scope.checkCountry = function() {
		if ($scope.srvc.checkCountry($scope.country.name)) {
			alert($scope.country.name + ' is in the list');
		} else {
			alert($scope.country.name + ' is not in the list');
		}
	};
});

appCtrl.spec.js:

describe('appCtrl', function(){
	var appCtrl, $scope;
 
	beforeEach(function() { // executed before each 'it' is run
		module('app'); // load the module
 
		inject(function($controller, $rootScope) { // inject controller for testing
			$scope = $rootScope.$new();
			appCtrl = $controller('appCtrl', {
				$scope: $scope
			});
		})
	});
 
	it('should have appCtrl controller toBeDefined', function() {
		expect(appCtrl).toBeDefined();
	});
 
	it('should init counter value', function() {
		expect($scope.count).toBeDefined();
		expect($scope.count).toBe(5);
 
	});
 
	it('should change counter value', function() {
		$scope.incrementCount();
		expect($scope.count).toBe(6);
	});
 
});

AngularJS service unit testing:

appSrvc.js:

app.factory('appSrvc', function () {
	return {
		countriesList: [
			{name: 'England', capital: 'London'},
			{name: 'Germany', capital: 'Berlin'},
			{name: 'Italy', capital: 'Rome'},
			{name: 'Spain', capital: 'Madrid'}
		],
		checkCountry: function(countryName) {
			var countryInList = false;
			angular.forEach(this.countriesList, function (value, index) {
				if (value.name === countryName) {
					countryInList = true; // country is in the list
				}
			});
			return countryInList;
		}
	};
});

appSrvc.spec.js:

describe('appSrvc', function(){
	var appSrvc, $scope;
 
	beforeEach(function() { // executed before each 'it' is run
		module('app'); // load the module
 
		// The _underscores_ are a convenience thing
		// so you can have your variable name be the
		// same as your injected service.
		inject(function(_appSrvc_) { // inject service for testing
			appSrvc = _appSrvc_;
		});
	});
 
	it('should provide a countriesList array property', function () {
		expect(appSrvc.checkCountry).toBeDefined();
		expect(appSrvc.countriesList instanceof Array).toBe(true);
	});
 
	it('should provide a myMethod function', function () {
		expect(appSrvc.checkCountry).toBeDefined();
		expect(typeof appSrvc.checkCountry).toBe('function');
	});
 
	it('should pass all test cases', function () {
		var i, valid, invalid;
 
		// test cases - testing for success
		var validCountries = [
			'England',
			'Germany'
		];
 
		// test cases - testing for failure
		var invalidCountries = [
			'123',
			''
		];
 
		// loop through arrays of test cases
		for (i in validCountries) {
			valid = appSrvc.checkCountry(validCountries[i]);
			expect(valid).toBeTruthy();
		}
		for (i in invalidCountries) {
			invalid = appSrvc.checkCountry(invalidCountries[i]);
			expect(invalid).toBeFalsy();
		}
	});
 
});

AngularJS directive unit testing:

appDrct.js:

app.directive('classWell', function() {
	return function(scope, element) {
		element.addClass('well');
	}
});

appDrct.spec.js:

describe('appDrct', function() {
	var element, $scope;
 
	beforeEach(function() { // executed before each 'it' is run
		module('app'); // load the module
 
		inject(function($compile, $rootScope) { // inject directive for testing
			$scope = $rootScope;
			element = angular.element('<div class-well>{{2 + 2}}</div>');
			$compile(element)($rootScope);
		})
	});
 
	it('should equal 4', function() {
		$scope.$digest();
		expect(element.html()).toBe('4');
	});
 
	it('should add a class of well', function() {
		expect(element.hasClass('well')).toBe(true);
	});
});

AngularJS filter unit testing:

appFltr.js:

app.filter('reverse', function(){
	return function(text){
		return text.split('').reverse().join('');
	}
});

appFltr.spec.js:

describe('reverseFltr', function() {
	var reverseFltr;
 
	beforeEach(function() { // executed before each 'it' is run
		module('app'); // load the module
 
		inject(function($filter) { // inject filter for testing
			reverseFltr = $filter('reverse');
		});
	});
 
	it('should have reverseFltr filter toBeDefined', function() {
		expect(reverseFltr).toBeDefined();
		//expect(!!reverseFltr).toBe(true);
	});
 
	it('should reverse the text', function () {
		expect(reverseFltr('abc')).toEqual('cba');
	});
 
});

Karma config:

karma.conf.js:

// Karma configuration
// http://karma-runner.github.io/0.12/config/configuration-file.html
 
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'],
 
 
        // list of files / patterns to load in the browser
        files: [
	        'js/vendor/angular.js',
	        //'js/vendor/*.js',
            'js/vendor/angular-mocks.js',
 
            // scripts
	        'js/app.js',
            'js/app*.js',
	        //'js/appCtrl.js',
	        //'js/appDctv.js',
 
 
	        'tests/**/*spec.js'
	        //'tests/unit/appCtrl.spec.js'
	        //'tests/unit/appDctv.spec.js'
        ],
 
 
        // list of files to exclude
        exclude: [
            'tests/coverage/**/*.js'
        ],
 
 
        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
            'js/**/*.js': ['coverage']//,
            // 'js/config/**/*.js' : ['coverage']
        },
 
 
        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['progress', 'coverage', 'html'],
 
 
        // 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: true,
 
        coverageReporter: {
            type: 'html',
            dir: 'tests/coverage/'
        },
 
        htmlReporter: {
            outputDir: 'tests/karma_html_report',
            templatePath: __dirname + '/jasmine_template.html'
        },
 
        // 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: false
    });
};

Grunt config:

Gruntfile.js:

module.exports = function(grunt) {
 
    var JSfiles = ['js/*.js'];
 
    // Project configuration.
    grunt.initConfig({
        // pkg: grunt.file.readJSON('package.json'),
        appConfig: grunt.file.readJSON('package.json'), // app.json
 
 
 
        html2js: {
            // options: {
            //     base: 'VersionedResources'
            // },
            main: {
                src: ['js/**/*.html'],
                dest: 'js/config/templates.js'
            }
        },
 
        jshint: {
            all: JSfiles,
            options: { // http://www.jshint.com/docs/options/
                globalstrict: false, // removes all 'use strict' errors
                curly: true, // requires you to always put curly braces around blocks in loops and conditionals
                eqeqeq: true, // prohibits the use of == and != in favor of === and !==
                freeze: true, // prohibits overwriting prototypes of native objects such as Array, Date and so on
                indent: 4, // enforces specific tab width for your code
                latedef: true, // prohibits the use of a variable before it was defined
                quotmark: 'single', // enforces to use 'single' or 'double' quotes
                maxdepth: 4, // lets you control how nested do you want your blocks to be
                maxlen: 200, // lets you set the maximum length of a line
                eqnull: true, // option suppresses warnings about == null comparisons
                browser: true, // defines globals exposed by modern browsers
                globals: {
                    jQuery: true // defines globals exposed by the jQuery JavaScript library
                }
            }
        },
 
        eslint: {
            all: {
                options: { // https://github.com/eslint/eslint/blob/master/docs/rules/README.md
                    config: 'eslint.json',
                    rulesDir: './'
                },
                src: JSfiles
            }
        },
 
        plato: {
            unittesting: {
                files: {
                    'reports': JSfiles
                }
            }
        },
 
        karma: {
            unit: {
                configFile: 'tests/config/karma.conf.js'
            },
            e2e: {
                configFile: 'tests/config/e2e.js'
            }
        },
 
        watch: {
            js: {
                files: JSfiles,
                tasks: ['jshint', 'eslint']
            }
        }
 
    });
 
 
    // Load the plugins
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-lesslint');
    grunt.loadNpmTasks('grunt-contrib-csslint');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('eslint-grunt');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-karma');
    grunt.loadNpmTasks('grunt-plato');
    grunt.loadNpmTasks('grunt-html2js');
 
 
    // Default task
    grunt.registerTask('default', ['less']);
    grunt.registerTask('js', ['jshint', 'eslint']);
    grunt.registerTask('tests', ['karma:unit']);
 
};

package.json:

{
  "name": "AngularJS unit testing",
  "description": "AngularJS unit testing",
  "version": "1.0.0",
  "devDependencies": {
    "underscore": "latest",
    "grunt": "latest",
    "grunt-contrib-watch": "latest",
    "grunt-contrib-less": "latest",
    "grunt-lesslint": "latest",
    "grunt-contrib-csslint": "latest",
    "grunt-contrib-jshint": "latest",
    "eslint-grunt": "latest",
    "grunt-contrib-copy": "latest",
    "karma-script-launcher": "latest",
    "karma-chrome-launcher": "latest",
    "karma-jasmine": "latest",
    "karma-phantomjs-launcher": "latest",
    "karma": "latest",
    "karma-story-reporter": "latest",
    "grunt-karma": "latest",
    "karma-coverage": "latest",
    "karma-sauce-launcher": "latest",
    "karma-html-reporter": "^0.2.3",
    "grunt-plato": "^1.0.0",
    "grunt-html2js": "^0.2.7"
  }
}

eslint.json:

{
  "rules": {
    "no-alert": 1,
    "no-caller": 1,
    "no-bitwise": 1,
    "no-console": 1,
    "no-debugger": 1,
    "no-empty": 1,
    "no-eval": 1,
    "no-floating-decimal": 1,
    "no-with": 1,
    "no-unreachable": 1,
    "no-undef": 0,
    "no-undef-init": 1,
    "no-octal": 1,
    "no-new-wrappers": 1,
    "no-new": 1,
    "no-underscore-dangle": 0,
 
    "strict": 0,
    "smarter-eqeqeq": 0,
    "brace-style": 1,
    "camelcase": 0,
    "curly": 1,
    "eqeqeq": 1,
    "new-parens": 1,
    "guard-for-in": 1,
    "new-cap": 1,
    "quotes": [2, "single", "avoid-escape"],
    "quote-props": 0,
    "semi": 1,
    "use-isnan": 1,
    "no-array-constructor": 1,
    "no-new-object": 1
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *