Wednesday, January 28, 2015

Unit Testing AngularJS Controllers with Mocked Promises

Overview:
Recently, I have been playing around with Javascript and AngularJS. Predominantly, being a backend developer, this was a good challenge.
And prior I had no extensive experience on Javascript nor AngularJS. So, I am a newbie.

However, I thought to blog some of my learnings, specially when it comes to testing the code.

This tutorial only touches the basic essence of mocking promises in Javascript code when unit testing.

The sample code for this project can be found here.

Prerequisites 

- NodeJS installed. You can find out more at http://nodejs.org/
- Git

Step 1: Cloning the project

$ git clone https://github.com/fazlan/angularjs.git

Step 2: Moving to the sample folder

$ cd angularjs/angular-mock-promises-sample

Step 3: Installing required node dependencies


{
   "name": "mock-service",
   "version": "1.0.0",
   "description": "",
   "author": "",
   "license": "ISC",
   "devDependencies": {
      "jasmine-core": "^2.1.3",
      "karma": "^0.12.31",
      "karma-jasmine": "^0.3.5",
      "karma-phantomjs-launcher": "^0.1.4",
      "phantomjs": "^1.9.13"
   }
}
 
$ npm install -g karma-cli  

The above is optional. You can skip the above if you have already installed karma-cli globally

$ npm install       

This will install all the node dependencies in the package.json

Step 4: Installing required bower dependencies
 
{
   "name": "mock-service",
   "version": "0.0.0",
   "license": "MIT",
   "devDependencies": {
      "angular": "~1.3.11",
      "angular-mocks": "~1.3.11"
   }
}

$ npm install -g bower 

The above is optional. You can skip the above if you have already installed bower globally

$ bower install       

This will install all the bower dependencies in the bower.json

Step 5: Karma configuration


// Karma configuration
// Generated on Tue Jan 27 2015 14:23:24 GMT+1100 (Tasmania Daylight Time)

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: [
        'bower_components/angular/angular.js',
        'bower_components/angular-mocks/angular-mocks.js',
        'app/src/**/*.js',
        'app/test/**/*_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'],


    // 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
  });
};


Step 6: Jasmine test with mocks


'use strict';
describe('The Mocked Weather Controller', function () {
    var scope;

    beforeEach(function () {

        // load the module
        angular.mock.module('weather.module');

        // inject the angular services
        inject(function ($controller, $rootScope, $q) {
            scope = $rootScope.$new();
            // mock the promise via $q service of angular
            var mockedOpenweatherMapAPI = {
                getWeather: function () {
                    var deferred = $q.defer();
                    deferred.resolve(openweatherMapJsonResponse());
                    return deferred.promise;
                }
            };
            $controller('WeatherController', {$scope: scope, WeatherService: mockedOpenweatherMapAPI});
        });
    });

    it('Returns the name of the city', function () {
        scope.$digest();
        expect(scope.weather).toEqual('Melbourne');
    });

    it('Returns the temperature details of Melbourne', function () {
        scope.$digest();
        expect(scope.temperature.temp).toEqual(23.3);
        expect(scope.temperature.humidity).toEqual(45);
        expect(scope.temperature.temp_min).toEqual(21.3);
        expect(scope.temperature.temp_max).toEqual(25.4);
    });

    it('Returns the weather summary details of Melbourne', function () {
        scope.$digest();
        expect(scope.weatherInfo.main).toEqual('Clear');
        expect(scope.weatherInfo.description).toEqual('Sky is Clear');
    });

    function openweatherMapJsonResponse() {
        return { "list": [
            {
                "name": "Melbourne",
                "main": {
                    "temp": 23.3,
                    "humidity": 45,
                    "pressure": 1016.2,
                    "temp_min": 21.3,
                    "temp_max": 25.4
                },
                "weather": [
                    {
                        "id": 800,
                        "main": "Clear",
                        "description": "Sky is Clear",
                        "icon": "01d"
                    }
                ]
            }
        ]};
    }
});

NOTE: Make sure to call the $digest() method on your scope variable on each test before you expect() results.

scope.$digest()

Step 7: Running the Jasmine tests
 
$ karma start karma.config.js
 
You'll see all your tests being run and their results.

Summary:
In this article, we looked at how to test promises in Javascript with mocked data. You can find the sample code for this post here.

Tuesday, January 27, 2015

Unit Testing AngularJS with Mocked Services

Overview:
Recently, I have been playing around with Javascript and AngularJS. Predominantly, being a backend developer, this was a good challenge.
And prior I had no extensive experience on Javascript nor AngularJS. So, I am a newbie.

However, I thought to blog some of my learnings, specially when it comes to testing the code.

This tutorial only touches the basic essence of mocking certain Javascript codes when unit testing.

The sample code for this project can be found here.

Prerequisites 

- NodeJS installed. You can find out more at http://nodejs.org/
- Git

Step 1: Cloning the project

$ git clone https://github.com/fazlan/angularjs.git

Step 2: Moving to the sample folder

$ cd angularjs/angular-mock-service-sample

Step 3: Installing required node dependencies


{
   "name": "mock-service",
   "version": "1.0.0",
   "description": "",
   "author": "",
   "license": "ISC",
   "devDependencies": {
      "jasmine-core": "^2.1.3",
      "karma": "^0.12.31",
      "karma-jasmine": "^0.3.5",
      "karma-phantomjs-launcher": "^0.1.4",
      "phantomjs": "^1.9.13"
   }
}
 
$ npm install -g karma-cli  

The above is optional. You can skip the above if you have already installed karma-cli globally

$ npm install       

This will install all the node dependencies in the package.json

Step 4: Installing required bower dependencies
 
{
   "name": "mock-service",
   "version": "0.0.0",
   "license": "MIT",
   "devDependencies": {
      "angular": "~1.3.11",
      "angular-mocks": "~1.3.11",
      "angular-loader": "~1.3.11"
   }
}

$ npm install -g bower 

The above is optional. You can skip the above if you have already installed bower globally

$ bower install       

This will install all the bower dependencies in the bower.json

Step 5: Karma configuration


// Karma configuration
// Generated on Tue Jan 27 2015 14:23:24 GMT+1100 (Tasmania Daylight Time)

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: [
        'bower_components/angular/angular.js',
        'bower_components/angular-route/angular-route.js',
        'bower_components/angular-mocks/angular-mocks.js',
        'app/src/**/*.js',
        'app/test/**/*_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'],


    // 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
  });
};


Step 6: Jasmine test with mocks


'use strict';
describe('The Mocked User Controller', function () {
    var scope;

    beforeEach(function () {

        // load the module
        module('user.module', function ($provide) {
            // mock the angular service
            var mockedUserService = {};
            mockedUserService.getUsers = function () {
                return ['user1', 'user2'];
            };
            // inject the mocked angular services
            $provide.value('UserService', mockedUserService);
        });

        // inject the angular services
        inject(function ($controller, $rootScope) {
            scope = $rootScope.$new();
            $controller('UserController', {$scope: scope});
        });
    });

    it('Returns the list of users', function () {
        expect(scope.users).toContain('user1', 'user2');
    });
});


Step 7: Running the Jasmine tests
 
$ karma start karma.config.js
 
You'll see all your tests being run and their results.

Summary:
In this article, we looked at how to test Javascript code using Jasmine. You can find the sample code for this post here.

Unit Testing with Jasmine and Karma

Overview:
Recently, I have been playing around with Javascript and AngularJS. Predominantly, being a backend developer, this was a good challenge.

And prior I had no extensive experience on Javascript nor AngularJS. So, I am a newbie.

However, I thought to blog some of my learnings, specially when it comes to testing the code.

This tutorial only touches the basic essence of Jasmine and Karma for testing your Javascript code.

The sample code for this project can be found here.
Prerequisites 

NodeJS installed. You can find out more at http://nodejs.org/

Step 1: Creating working directory

$ mkdir jasmine-karma-sample
$ cd jasmine-karma-sample

Step 2: Installing required node dependencies

$ npm install -g karma-cli

Above is installed as a global (-g) dependency

$ npm install phantomjs --save-dev
$ npm install karma-jasmine --save-dev
$ npm install karma-phantomjs-launcher --save-dev

Step 3: Creating Karma configuration file

You can learn more about Karma at http://karma-runner.github.io/

$ karma init

NOTE: This will prompt a series of questions and accept the defaults, and update the file as follows

// Karma configuration
// Generated on Tue Jan 27 2015 13:04:51 GMT+1100 (Tasmania Daylight Time)

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: [
        'app/src/**/*.js',
        'app/test/**/*_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'],


    // 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
  });
};

Step 4: Verifying the project structure

jasmine-karma-sample
   |
   |- node_modules  // this is created as a result of npm install commands
   |
   `- karma.config.js  // this is created as a result of karma init command

Step 5: Writing Jasmine spec

jasmine-karma-sample/app/test/basic_spec.js

'use strict';


describe('Basic Jasmine Syntax', function() {

    describe('Some simple Jasmine constructs', function() {
        it('expects 2 to be 2', function() {
            expect(2).toBe(2);
        });

        it('expects Foo and Bar are not equal', function() {
            expect('Foo').not.toEqual('Bar');
        })

        it('checks if variable is defined', function() {
            var variable = 1;
            expect(variable).toBeDefined();
        })

        it('checks if variable is undefined', function() {
            var variable;
            expect(variable).toBeUndefined();
        })
    });

    describe('an Array', function() {
        var theArray;

        beforeEach(function() {
            theArray = ['Karma', 'Jasmine'];
        });

        it('checks if the array is not empty', function() {
            expect(theArray).toContain('Karma', 'Jasmine');
        });

        it('adds elements to the array', function() {
            theArray.push('PhantomJS');
            expect(theArray).toEqual(['Karma', 'Jasmine', 'PhantomJS']);
        });

        it('removes elements from the array', function() {
            theArray.pop('Jasmine');
            expect(theArray).toEqual(['Karma']);
        })
    });

});


This file contains the symmetric of a Jasmine specification. You can learn more about Jasmine at http://jasmine.github.io/

Step 6: Running the Jasmine tests

$ karma start karma.config.js

You'll see all your tests being run and their results.

Step 6: Writing more Jasmine tests

jasmine-karma-sample/app/test/calculator_spec.js

'use strict';


describe('a Calculator', function () {

    var theCalculator = org_namespace.Calculator;

    beforeEach(function () {
        theCalculator.reset();
    });

    it('checks if the calculator has been reset', function () {
        expect(theCalculator.result).toBe(0);
    });

    it('adds two integers', function () {
        theCalculator.add(10, -30);
        expect(theCalculator.result).toBe(-20);
    })

    it('multiplies two integers', function () {
        theCalculator.multiply(10, 5);
        expect(theCalculator.result).toBe(50);
    })
});
 
jasmine-karma-sample/app/src/calculator_spec.js

'use strict';

var org_namespace = {};
org_namespace.Calculator = {
    result: 0,
    add: function (x, y) {
        this.result = x + y;
    },

    multiply: function (x, y) {
        this.result = x * y;
    },

    reset: function () {
        this.result = 0;
    }
};

Summary:
In this article, we looked at how to test Javascript code using Jasmine. You can find the sample code for this post here.