Test Online REST API using Karma & Jasmine

Today we are going to add test for Online REST API. For that we are using JSONPlaceholder Fake Online REST API.

Let's start the testing. First of all we are going to write our test for angular.factory called jsonplaceholder.spec.js.

describe('JSON Placeholder API Service', function () {
    var API_ENDPOINT = 'https://jsonplaceholder.typicode.com';

    beforeEach(angular.mock.module('api.jsonplaceholder'));

    beforeEach(inject(function (_JsonPlaceholderApi_, _$httpBackend_, _$q_){
        JsonPlaceholderApi = _JsonPlaceholderApi_;
        $httpBackend = _$httpBackend_;
        $q = _$q_;
    }));

    it(' Should Exists', function (){
        expect(JsonPlaceholderApi).toBeDefined();
    });
});

Now if you run karma start you will see something like that:
screen-shot-2017-11-06-at-11-19-06-am

which means, we haven't defined our angular module with factory. Lets defined that:

(function (){
    'use strict';
    angular.module('api.jsonplaceholder', []).factory('JsonPlaceholderApi', jsonPlaceholderApi);
    jsonPlaceholderApi.$inject = ['$http', '$q'];

    function jsonPlaceholderApi($http, $q) {
        return this;
    };
}());

This time you will see something like that:
screen-shot-2017-11-06-at-11-21-10-am

Now, lets make a GET call to REST API end point /posts. It will return 100 post item as JSON response. On Success, here we will mock the response with a mockObject which length is 3.

describe('JSON Placeholder API Service', function () {
    var API_ENDPOINT = 'https://jsonplaceholder.typicode.com',
        mockResponse = [
            {
                "userId": 1,
                "id": 1,
                "title": "sunt aut facere repellat provident occaecati excepturi",
                "body": "quia et suscipitnsuscipit recusandae consequuntur expedita"
            },
            {
                "userId": 1,
                "id": 2,
                "title": "qui est esse",
                "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea "
            },
            {
                "userId": 1,
                "id": 3,
                "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
                "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi "
            }];

    beforeEach(angular.mock.module('api.jsonplaceholder'));

    beforeEach(inject(function (_JsonPlaceholderApi_, _$httpBackend_, _$q_){
        JsonPlaceholderApi = _JsonPlaceholderApi_;
        $httpBackend = _$httpBackend_;
        $q = _$q_;
    }));

    it(' Should Exists', function (){
        expect(JsonPlaceholderApi).toBeDefined();
    });

    describe('GET /Post ', function () {
        var result;

        beforeEach(function () {
            result = {};
            spyOn(JsonPlaceholderApi, 'getAll').and.callThrough();
        });

        it('should return All Posts ', function () {
            $httpBackend.whenGET(API_ENDPOINT + '/posts')
                                 .respond(200, $q.when(mockResponse));

            expect(JsonPlaceholderApi.getAll).not.toHaveBeenCalled();

            JsonPlaceholderApi.getAll().then(function (response) {
                result = response;
            });

            $httpBackend.flush();
            expect(JsonPlaceholderApi.getAll).toHaveBeenCalled();
            expect(result.length).toEqual(3);
            expect(Object.keys(result[0]).length).toEqual(4);
        });

    });
});

Now, if you run your test you will see an error with getAll method is not defined. Let's define that method:

(function (){
    'use strict';
    angular.module('api.jsonplaceholder', []).factory('JsonPlaceholderApi', jsonPlaceholderApi);
    jsonPlaceholderApi.$inject = ['$http', '$q'];

    function jsonPlaceholderApi($http, $q) {
        var jsonPlaceholderApi = {},
            API_ENDPOINT = 'https://jsonplaceholder.typicode.com';

        jsonPlaceholderApi.getAll = function () {
            return $http.get(API_ENDPOINT + '/posts').then(function (res){
                return res.data;
            });
        };
        return jsonPlaceholderApi;
    };
}());

That's it. Now you can write as many test as you want.

Test Yourself
- write a test to find an item using following REST API GET /posts/1;

it('should return a POST When called with a valid Id', function (){
    //@TODO: write your test
});

Test angular controller using Karma and Jasmine

In my last blog post, I talked about “Test angular service using Karma and Jasmine”. Today I am going to talk about “Test angular controller using Karma and Jasmine”.

Testing Controller

Lets create a controller test file users.js and do as follows:

describe("UserController", function () {
});

Let’s mock our angular module component.users using angular.mock in the following way:

describe("UserController", function () {
    beforeEach(angular.mock.module('component.users'));
});

To identify the controller users.js we need to add the dependency (inject) to our angular.mock.module in the following way: 

beforeEach(inject(function (_$controller_){
    $controller = _$controller_;
    UsersController = $controller('UsersController', {});
}));

Here we two undefined variable $controller and UsersController. Lets defined them: 

beforeEach(inject(function (_$controller_){
    var $controller, UsersController;
    $controller = _$controller_;

    UsersController = $controller('UsersController', {});
}));

Now lets start our first test case to check whether the controller is defined or not. To do so, follow this:

describe('UsersController', function () {
    var $controller, UsersController, userService;
    beforeEach(angular.mock.module('component.users'));

    beforeEach(inject(function (_$controller){
        $controller = _$controller_;

        UsersController = $controller('UsersController', {});
    }));

    it (' should be defined ', function () {
        expect(UsersController).toBeDefined();
    });
});

Now if we run the following command:

# karma start

it will display error with controller file name. To resolve this error lets create our controller file users.js:

(function (){
    'use strict';

    angular.module('component.users', []).controller('UsersController', usersController);
    usersController.$inject = []

    function usersController() {
        var vm = this;
    }

}());

This time, you will see test case passed:

Chrome 56.0.2924 (Mac OS X 10.12.0): Executed 1 of 1 SUCCESS (0.062 secs / 0.083 secs)
TOTAL: 1 SUCCESS

Testing Controller with Service Dependency

Let's add the dependency user-service.js (that we created in my last blog post) to the controller and write the test for that.

First modify users.spec.js file as follows:

describe('UsersController', function () {
    var $controller, UsersController, userService; // add this

    beforeEach(angular.mock.module('component.users'));
    beforeEach(angular.mock.module('component.users.service')); //add this

    beforeEach(inject(function (_$controller_, _UserService_) { // add this
        $controller = _$controller_;
        userService = _UserService_; // add this

        UsersController = $controller('UsersController', {'UserService': userService}); // add this
    }));

    it (' should be defined ', function () {
        expect(UsersController).toBeDefined();
    });
});

Now if you run # karma start you will see an error, like that

Error: [$injector:unpr] Unknown provider: UserServiceProvider <- UserService <- UsersController

To fix this error, modify users.js controller by adding the service dependency:

(function (){
    'use strict';

    angular.module('component.users', []).controller('UsersController', usersController);
    usersController.$inject = ['UserServic1e']

    function usersController(UserService) {
        var vm = this;
    }
}());

This time you not see any more error.
Now lets define a method userService.all() in service and try to access it in controller users.js.

Update the controller test file users.spec.js file:

describe('UsersController', function () {
    var $controller, UsersController, userService;

    beforeEach(angular.mock.module('component.users'));
    beforeEach(angular.mock.module('component.users.service'));

    beforeEach(inject(function (_$controller_, _UserService_){
        $controller = _$controller_;
        userService = _UserService_;

        UsersController = $controller('UsersController', {'UserService': userService});
    }));

    it (' should be defined ', function () {
        expect(UsersController).toBeDefined();
    });

    // add this Test Case
    it('should Initlialized with a call to UserService.all()', function (){
        expect(userService.all).toHaveBeenCalled();
    });
});

This time if you run # karma start, you will see an error like that:

screen-shot-2017-10-30-at-3-48-46-pm

To resolve this error we are going to use spyOn method of Jasmine. Click here to know more about spyOn,

describe('UsersController', function () {
    var $controller, UsersController, userService;

    beforeEach(angular.mock.module('component.users'));
    beforeEach(angular.mock.module('component.users.service'));

    beforeEach(inject(function (_$controller_, _UserService_){
        $controller = _$controller_;
        userService = _UserService_;

        spyOn(userService, 'all').and.callFake(function (){
            return [{ id: '1', name: 'Masud', role: 'Developer', location: 'CA', twitter: 'masudiiuc' }];
        });

        UsersController = $controller('UsersController', {'UserService': userService});
    }));

    it (' should be defined ', function () {
        expect(UsersController).toBeDefined();
    });

    it('should Initlialized with a call to UserService.all()', function (){
        expect(userService.all).toHaveBeenCalled();
        expect(userService.all()).toEqual(userList);
    });
});

Now if you run # karma start you will see the following error:
screen-shot-2017-10-30-at-3-57-49-pm

Let's define the method in our user-service.js file:

(function (){
    'use strict';
    angular.module('component.users.service', []).service('UserService', userService);

    function userService() {
        this.all = all;
        function all(){
            return [{ id: '1', name: 'Masud', role: 'Developer', location: 'CA', twitter: 'masudiiuc' }];
        }
    };
}());

Now if you run # karma start you will not see any error. Now add another Test Case like that:


it('should Initlialized with a call to UserService.all()', function (){ expect(userService.all).toHaveBeenCalled(); expect(userService.all()).toEqual( [{ id: '1', name: 'Masud', role: 'Developer', location: 'CA', twitter: 'masudiiuc' }] ); // add this });

That's it. Now enjoy Testing your angular application.

Help:
- Testing AngularJS with Jasmine and Karma
- Jasmine SpyOn

Continue......

If you want to explore my playground, follow this github repository: https://github.com/masudiiuc/angular-unit-test

Testing angularjs with Jasmine and Karma

Karma is one of the best Test runner suite and mostly created and used by angularJS team. Jasmine is one of the best Behaviror driven testing framework. Jasmine is also dependency free and doesn’t require a DOM.

Today, I am going to show how to test a angular service/factory using Karma and Jasmine.

Requirements
To start with Testing we need some node package needs to be installed. To install the packages run the following commands
- To install Jasmin: npm install jasmine-core
- To install Karma: npm install karma
- To integrate karman with jesmine: npm install karma-jasmine
- To run karma with chrome browser: npm install karma-chrome-launcher
- Finally, we need Karma Command line tools: npm install karma-cli

Let's assume you have an existing angularJS application. To initialize Karma in your application, run this command:

# karma init

which will drive you through some configuration steps like this:
screen-shot-2017-10-27-at-11-55-48-am

This will generate a file called karma.conf.js with some default settings in your root directory. Now open the file in your favorite editor.

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'], 
    files: [
        './node_modules/js/angular.js', // add this 
        './node_modules/js/angular-mocks.js', // add this
    ],
    exclude: [],
    preprocessors: {},
    reporters: ['progress'], 
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    concurrency: Infinity
  });
}

Now we are ready for test our application with Karma and Jasmine.

Testing angular service/factory

Lets start our first test code with angular service. Assume that we are about to write our first service userService.js. As TDD means Test Driven Development, we should start with our test file called userService.spec.js

Test File: public\test\app\js\service\userService.spec.js

describe('User Service', function () {
    beforeEach(angular.mock.module('api.users'));
    beforeEach(inject(function (_Users_){
        Users = _Users_;
    }));

    it('should exits ', function (){
        expect(Users).toBeDefined();
    });

    it('has a dummy spec to test 2+2', function (){
        expect(2+2).toEqual(4);
    });
});

Now our test file userService.spec.js is ready to execute. To execute the file, we need to include the files in our karma.conf.js file. Do as follows:

files: [
        './node_modules/js/angular.js', 
        './node_modules/js/angular-mocks.js',
        './public/test/app/js/service/userService.spec.js' //add this line
    ],

Now go to the root directory of your project and run the following command:

# karma start

It will through an error like this, as we don't have the source file with the module name.
Lets create the source file called userService.js.

Source File: public\app\js\service\userService.js

(function() {
    'use strict';
    angular.module('api.users', []).service('Users', function() {
        //@todo
    });
}());

This time if you run the following command karma start you will see something like this:

screen-shot-2017-10-27-at-11-57-03-am

which means, your service has passed the Test Cases. Congrates...

Help:
To the test coverage report in a meaningful way, do the followings:
1. install the following package npm install karma-spec-reporter
2. change in the karma.conf.js file from reporters: ['progress'], to reporters: ['spec'],
3. now run karma start and you will see something like this:

screen-shot-2017-10-27-at-3-53-20-pmContinue...

Create a customizable directive using UI-Select

UI-Select is one of the most popular module for front-end development. it has lots of customizable options, which help to make the UI layer more interactive. So rather than calling the same config thing all the time, why not create a own directive which will accept some params as attribute like field name and config options.

So I create the following directive. here is the way of doing that:

1. Template for the directive, foodropdown.html

2. wirte the directives. fooDropdown.js

3. Syntax of the directive:

4. To test the directive, pass the parameters, in the following way:

Simple Todo List with AngularJS > 1.3

Lets create a simple Todo list using AngularJS > 1.3. Nothing to say, the codes tells a lot. Lets write the code:

Step 01: Create a JS file called app.js and put the following code:

'use strict';

var todoAppliction = angular.module("todoApp",[]);

todoAppliction.controller("todoController", todoController);
todoController.$inject =['$scope'];

/**
 * Processs an input box enter event
 * Ex: on enter add the task in the list
 */
todoAppliction.directive('ngEnter', function() {
    return function(scope, element, attrs) {
        element.bind("keydown keypress", function(event) {
            if(event.which === 13) {
                scope.$apply(function(){
                    scope.$eval(attrs.ngEnter, {'event': event});
                });

                event.preventDefault();
            }
        });
    };
});


/**
 * Manage todolist functionality
 */
function todoController($scope){
    
    $scope.todoList = [];
    $scope.isValid = true;
    $scope.totalCompleted = 0;
    $scope.totalInCompleted = 0;
    $scope.filterBy = 'all';
    
    
    $scope.addTodoItem = addTodoItem;
    $scope.removeTodoItem = removeTodoItem;
    $scope.getStatus =  getStatus;
    
    
    function addTodoItem(){
		$scope.isValid = isValid();
        if(!$scope.isValid) {
            return false;
        }
        
    	$scope.todoList.push({
        	id: 'id_' + (new Date()).getTime(),
            name: $scope.task_title, 
            completed: false, 
            createdDate: (new Date())
        });
        
        $scope.task_title = '';
    }
    
    function removeTodoItem(index) {
    	$scope.todoList.splice(index,1);
    }
    
    function isValid() {
    	return ($scope.task_title != null && $scope.task_title != undefined && $scope.task_title != "")
    }
    
    function getStatus(isComplete){
        var count = 0;
    	angular.forEach($scope.todoList, function(item){
			if (isComplete && item.completed) {
                count++;
            }else if (!isComplete && !item.completed) {
                count++;
            }
        })
        return count;
    }

}

Step 02: Create the template index.html and put the following code inside it:

<body>
    <div ng-app="todoApp" ng-controller="todoController">
        <div class="task-input">
            <input type="text" ng-enter="addTodoItem()" id="task-input-box" placeholder="enter task title" ng-model="task_title"/>
            <button type="button" name="add-task" id="add-task" class="button" ng-click="addTodoItem()">
                + Add 
            </button>
            <br/>
            <div class="error-mg" ng-show="!isValid">Please enter a title</div>
            
            <div id="filterOptions">

                <input type='radio' ng-model="filterBy" value="all"/> All ({{todoList.length}})
                    <input type='radio' ng-model="filterBy" value="complete"/> Complete ({{getStatus(true)}})
                        <input type='radio' ng-model="filterBy" value="incomplete"/> Incomplete ({{getStatus(false)}})
                    
            </div>
        </div>


        <div class="todo-list">
            <ul>
                <li ng-repeat="todo in todoList track by todo.id"
                    ng-show="(filterBy=='all') || (filterBy=='incomplete' && !todo.completed) || (filterBy=='complete' && todo.completed)"
                > 
                    <input type="checkbox" ng-model="todo.completed"/>
                    <span ng-class="todo.completed ? 'complete': 'incomplete'">{{todo.name}} </span>
                    <a href="#" ng-click="removeTodoItem($index)">remove</a>
                </li>
            </ul>
        </div> 
    
    </div>
</body>

Step 03: Style it with CSS in style.css

.task-input{
    padding: 10px;
    background-color: #4072B4;
    color:white;
    font-family:verdana;
    font-size: 12px;
}

.task-input input[type=text]{
    border: 1px solid white;
    padding: 10px;
    font-size: 15px;
    border-radius: 3px;
    width: 280px;
}

.button{
    padding: 11px 15px;
    font-size: 13px;
    border-radius: 3px;
    border: 1px solid white;
    background-color: #3267C6;
    color:white;
}

.error-mg{
    color:red;
    font-size:12px;
    text-align:left;
}

.complete{
    text-decoration:line-through;
    color:#999;
}

.todo-list{
    margin: 15px 0px;
}

.todo-list ul{
    padding:0;
    margin:0;
}

.todo-list ul li{
    list-style:none;
    padding: 0;
}

.todo-list ul li span{
    width: 320px;
    display:inline-block;
    vertical-align:top;
}

#filterOptions{
    margin: 10px 0px;
}

That's it. Enjoy and play with Todo list

Next: we will store the list in MongoDB and Categories Todo Item.