

Test driven development and behavior driven development are very important concepts in web development at this time.

Before writing any tests you need to set up your development environment by installing a few tools and configuring one or two files.

This blog post is based on Windows platform using Node.js and npm (Node Package Manager).

Jasmine is an established JavaScript unit testing framework and it has several built-in methods that are more like assert on steroids.

Karma is the testing framework from the AngularJS team at Google, it was formerly known as Testacular (good thing they changed that name).

Enough of the introductions, time for the fun stuff. First a caveat, due to the endless variations of developer environments, the steps outlined here might need a few adjustments. Things like system variables and paths to certain files may cause errors on your computer.

Run the following NPM commands from your project root folder to install the required node packages:

C:\project_root> npm install -g angular

C:\project_root> npm install -g angular-mocks

C:\project_root> npm install karma –save-dev

C:\project_root> npm install jasmine-karma jasmine-core –save-dev

C:\project_root> npm install karma-firefox-launcher –save-dev

(N.B you can also install other browsers like safari or chrome. The –save-dev argument adds the dependencies to the package.json file)

{ "name": "unittest", "version": "1.0.0", "description": "unit test demo", "main": "index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "mide", "license": "ISC", "devDependencies": { "angular-mocks": "^1.5.6", "grunt-contrib-concat": "^0.5.1", "grunt-contrib-jshint": "^0.11.3", "grunt-contrib-uglify": "^0.9.2", "grunt-contrib-watch": "^0.6.1", "gulp": "^3.9.1", "jasmine-core": "^2.4.1", "karma": "^0.13.22", "karma-firefox-launcher": "^1.0.0", "karma-jasmine": "^1.0.2" }, "dependencies": { "angular": "^1.5.6" } }

The Jasmine framework allows you to create test suites using specs. When applying this to an Angular project the advantages of Dependency Injection become crystal clear.

A typical karma.conf.js looks like the code below, and is generated automatically when you run the karma init command from terminal or command prompt (cmd.exe in Windows).

C:\project_root> karma init

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: [ 'lib/jquery-2.1.1.min.js', 'lib/moments.js', 'lib/moments.tz.js', 'node_modules/angular/angular.js', 'node_modules/angular-mocks/angular-mocks.js', 'lib/angulargoogleapi.js', 'lib/googleapi.js', 'js/ang/appcal.js', 'js/ang/util.js', 'js/ang/syncService.js', 'js/ang/offlineController.js', 'js/ang/onlineController.js', 'js/tests/*.spec.js' ], // list of files to exclude exclude: [ ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress', 'html'], htmlReporter:{ outputFile: 'js/tests/units.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, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['Firefox'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity }) }

Lines 13 – 26 above include paths to all relevant files for your unit tests. Starting with required libraries for your application, then your application files and then the unit test spec files.

A quick test can be implemented by saving the code below in a file named example.spec.js in the js/tests folder (see line 26 above). Execute the command karma start from the command prompt.

C:\project_root> karma start

describe("example unit test", function(){ it("should always be true", function(){ expect('test').toBe('test'); }) });

For testing AngularJS, you need a little more Jasmine code. As an illustration, take the bare bones mainCtrl controller in the ng-app myApp. It has one scope variable myData which is array. We can test for the length of the array or simply test that the variable has been defined.

angular.module("myApp",[]) .controller("mainCtrl", function($scope){ $scope.myData = [{"id":1,"first_name":"Michelle","last_name":"Duncan","email":"mduncan0@toplist.cz","country":"Mongolia","ip_address":"242.152.35.231"}, {"id":2,"first_name":"Peter","last_name":"Romero","email":"promero1@ibm.com","country":"Ethiopia","ip_address":"43.123.111.225"}, {"id":3,"first_name":"Ruby","last_name":"King","email":"rking2@ucoz.ru","country":"Pakistan","ip_address":"240.49.225.200"}, {"id":4,"first_name":"Tammy","last_name":"Garza","email":"tgarza3@squidoo.com","country":"Ireland","ip_address":"136.131.213.128"}]; });

In the unit test below, first you create a describe block for the myApp app. And then load the module using beforeEach. Then you have another describe block for the controller and this includes another beforeEach method with built in directives $controller and $rootScope injected using inject.

describe("myApp test", function(){ beforeEach(module('myApp')); describe("main controller", function(){ var scope, mainCtrl; beforeEach(inject(function($controller, $rootScope){ scope = $rootScope.$new(); mainCtrl = $controller('mainCtrl', { $scope:scope }) })) it("should test myData", function(){ scope.$digest(); expect(scope.myData).toBeDefined(); expect(scope.myData.length).toBe(4); expect(scope.myData[3].email).toBe('rking2@ucoz.ru'); }) }) });