Saturday, August 20, 2016

Unit Testing - Angular.js Using Jasmine

Introduction:-

Unit testing, as the name implies, is about testing individual units of code. Angular provides the way to test its code by writing unit test cases. Angular unit test cases can be written using Jasmine framework and can be run using karma test runner easily. the whole process of writing and executing test cases is covered in this document.

Dependencies:-
  • NodeJs
  • AngularJs
  • RequireJs
  • Jasmine
  • Karma

                                               Developer Setup Sample



Basic Configuration:-


First of all you need to install NodeJS to proceed. Please install NodeJS.

Start by installing Node package manager :

Create a folder inside your application for example i created named unit_tests. open command promt inside it and execute following command-

$ npm init



This will ask few options and will generate package.json file inside that folder.

Now execute following command for installing karma test runner and karma command line interface

$ npm install karma

$ npm install karma-cli

Install following dependencies of Jasmine and crome launcher required to launch Karma Test runner in the Chrome browser

$ npm install karma-jasmine

$ npm install karma-chrome-launcher

If your angular application is built on requirejs framework then you need to install requirejs dependency also using following command -

$ npm install requirejs

After executing above commands your folder structure will be like -

unit_tests
|--- node_modules
     |--- jasmine-core
     |--- karma
     |--- karma-chrome-launcher
     |--- karma-jasmine
     |--- karma-cli
     |--- requirejs
|--- package.json

now you need to initialize karma, for it execute following command-

$ karma init
(If karma command is not working then install it globally using command $ npm install -g karma-cli or execute 
$ ./node_modules/karma/bin/karma init)



This command will generate karma.conf.js and test-main.js files. various configuration options are asked during execution of this command as shown in above screen-shot.

During execution there will be an option for mentioning the location of source and test files. you can mention these files path here and also can modify later in karma.conf.js file. Please have a look into following screen-shot-



One more thing you need to consider in karma.conf.js file is basePath. this mention the root folder location from where karma will search for files mentioned in files array.


One more file named test-main.js will get generate if you are selecting "generate a bootstrap file for RequireJS".

You can mention paths in test-main.js under "require.config". paths is intended more for shortcuts/prefixs to simplify/configure includes, rather than full module paths. please view following screen-shot-


As shown in above screen-shot lib is mentioning the folder location of lib folder, angular is mentioning the location of angular.min.js file. basically we mention shortcuts for easy access of dependendent js files instead mentioning whole path of required file at spec file during dependency injection in define block or require block.

you can use shim also under "require.config" for defining order of dependencies, if one dependency is depend on another. for example in bellow given screen-shot apploader dependency is depend on app. so in application where ever the apploader will be called, it will load app first.



For running test cases you need to execute following command-

$ karma start

(If not working then execute $ ./node_modules/karma/bin/karma start)

How to write test cases:-


Now basic setup part has been completed. now in following section it is mentioned that how to write unit test cases-

Unit test cases are written inside describe block for e.g-

describe("A suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});

One more example is -

describe("A suite is just a function", function() {
  var a;
  it("and so is a spec", function() {
    a = true;
    expect(a).toBe(true);
  });
}); 

You can use beforeEach block if you want to run specific code before each test execution. similar afterEach block also. you can use nested describe also just for creating a proper test hierarchy. for Ex-

describe("A spec", function() {
  var foo;
  beforeEach(function() {
    foo = 0;
    foo += 1;
  });
  afterEach(function() {
    foo = 0;
  });
  it("is just a function, so it can contain any code", function() {
    expect(foo).toEqual(1);
  });
  it("can have more than one expectation", function() {
    expect(foo).toEqual(1);
    expect(true).toEqual(true);
  });
  describe("nested inside a second describe", function() {
    var bar;
    beforeEach(function() {
      bar = 1;
    });
    it("can reference both scopes as needed", function() {
      expect(foo).toEqual(bar);
    });
  });
});


Injecting dependencies inside define block - 

such as define block syntax is -

define(['dependency1','dependency2', 'pathToDependencyFromTheBaseFolder/dependency3' ], function(dep1, dep2, dep3){
// required code at here
});

Complete sample code for a test case using requirejs is -

define(['dependency1','dependency2', 'pathToDependencyFromTheBaseFolder/dependency3' ], function(dep1, dep2, dep3){
       describe("write test case description",function(){
              beforeEach(function(){
                     // Loading required module at here
                     module('myApp');
              });
             
              it("test description", function(){
                     var a = 1;
                     expect(a).toEqual(1);
              });
       });
});


Mocking a dependency -  

Mocking a Custom Service - 

Mocking is redefining a service with dummy data for the test cases execution tracking, if service data is very complex or it is being load from server. for example in the following screen shot i am mocking "AdminSupportService" service, because it is being configured in run time when data comes from server. but for writing unit tests we not need to load data from the server.



Mocking Http Service -

If ajax request is going to server so during writing test cases we don't need to send request. we can just do mocking of HttpBackend Service and can write expected response in that service. please have a look into following screen-shot -

 

And after calling the method in which ajax request is send we need to flush httpBackend service -

$httpBackend.flush();

Spying a method- 

If there is a method in a dependent service which we want to track, i.e. it has been called or not, or how many times it is being called, or we just want to track its calling and want some specific value in return, then we use spy for a method.

For Eg. If there is a service named "AdminValidator" and we want to track its method named "isValid". then we can use "spyOn" method of jasmine.

If you want to track a method and want some specific return value from it, then you can use following syntax-

spyOn(AdminValidator, 'isValid').and.returnValue(false);

If you want to track a method and want to execute its body also then you can use following syntax -

spyOn(AdminValidator, 'isValid').and.callThrough();

And if you want to track a method and want to call fake function instead of the original then you can use following syntax-

spyOn(ApplicationService, 'getLoginUser').and.callFake(function(){
       return {
              name:"Andrew Strauss"
       }
});

For more detail for writing test cases you can have a look into bellow url-



No comments:

Post a Comment