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 } } |