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 () {
            returnhttp.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_){
    varcontroller, 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…