Ad

Error When Trying To Unit Test A Basic Service On AngularJS

- 1 answer

I am new to unit testing AngularJS with Jasmine. I am trying to set up a very simple environment.

This is my karma.conf.js file:

module.exports = function(config) {
  config.set({

    basePath: '',
    frameworks: ['jasmine'],
    files: [
        'angular/angular.min.js',
        '../node_modules/angular-mocks/angular-mocks.js',
        'app.js',
        'app-services/*.js',
        'app-services-tests/*.js'
    ],
    exclude: [
    ],
    preprocessors: {
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: false,
    concurrency: Infinity
  })
}

In one of the function, I make an ajax call, so as far as I understand, I can do something like this (this is my test file):

describe('factory: ProductService',function(){
    var ProductService;
    beforeEach(inject(function(_ProductService_){
        ProductService = _ProductService_;
    }));
    describe('factory: ProductService',function(){
        var ProductService;
        beforeEach(inject(function(_ProductService_){
            ProductService = _ProductService_;
        }));

        var categorizedProducts = angular.fromJson('{ "ErrorMessage": null, "Result": { "0": { "CliClientId": "016VK9B90U", "InvProductCategoryId": "0LQIHDH2EM", "CategoryName": "LACA", "InvProductCategoryParentId": null, "ModifiedDate": "2016-01-28 16:36:37", "products": { "0": { "CliClientId": "016VK9B90U", "InvProductId": "030LFCE9KV", "InvProductCategoryId": "0LQIHDH2EM", "Name": "1055-B020 LACA NITRO MATE 4 LTS", "Description": "1055-B020 LACA NITRO MATE 4 LTS-VALRESA", "Barcode": null, "InternalCode": "L105525", "SaleUnitType": "Piece", "ApplicableTaxKeys": null, "Status": "Active", "CreatedDate": "2016-01-28 16:36:39", "CreatedBy": "0000000000", "ModifiedDate": "2016-01-28 16:36:39", "ModifiedBy": "0000000000", "CliLocationId": "2I1G4IHPOB", "prices": null } } }, "1": { "CliClientId": "016VK9B90U", "InvProductCategoryId": "W27Q71D4XJ", "CategoryName": "BROCHAS", "InvProductCategoryParentId": null, "ModifiedDate": "2016-01-28 16:36:38", "products": { "0": { "CliClientId": "016VK9B90U", "InvProductId": "0B23HD5PGL", "InvProductCategoryId": "W27Q71D4XJ", "Name": "BROCHA FAJA ROJA 2 1\/2", "Description": "BROCHA FAJA ROJA 2 1\/2", "Barcode": null, "InternalCode": "BFR2.5", "SaleUnitType": "Piece", "ApplicableTaxKeys": null, "Status": "Active", "CreatedDate": "2016-01-28 16:36:38", "CreatedBy": "0000000000", "ModifiedDate": "2016-01-28 16:36:38", "ModifiedBy": "0000000000", "CliLocationId": "2I1G4IHPOB", "prices": null }, "1": { "CliClientId": "016VK9B90U", "InvProductId": "0FQO5KJMX7", "InvProductCategoryId": "W27Q71D4XJ", "Name": "BROCHA LA BUENA 2\"", "Description": "BROCHA LA BUENA 2\"-BYP", "Barcode": null, "InternalCode": "BBU2", "SaleUnitType": "Piece", "ApplicableTaxKeys": null, "Status": "Active", "CreatedDate": "2016-01-28 16:36:38", "CreatedBy": "0000000000", "ModifiedDate": "2016-01-28 16:36:38", "ModifiedBy": "0000000000", "CliLocationId": "2I1G4IHPOB", "prices": null } } } } }');
        it("should return json with categorized products", function () {
            httpBackend.whenGET("http://test.dev/v1/getPOSProducts/E59576F6-E25A-4261-B1CA-66A7049C11DD").respond(categorizedProducts);
        });
    });
});

Finally, this is my product service:

/**
 * Product service.
 *
 * Service that manages products. Including offline mode and sync tasks.
 *
 * @param {!angular.Http} $http
 * @param {!angular.RootScope} $rootScope
 * @ngInject
 * @export
 */
(function () {
    'use strict';
    angular
        .module('inspinia')
        .factory('ProductService', ProductService);
    /**
     * clientUpdatesQueue
     *
     * Will hold all the offline operations on the following format:
     * {'Section':SectionEnum.SECTION, 'Operation':OperationEnum.OPERATION, 'Record':record, 'Timestamp':currentTimeStamp}
     */
    var clientUpdatesQueue = [];
    var products = [];
    /**
     * Enum for sections used by the clientUpdatesQueue array.
     */
    var SectionEnum = {
        PRODUCTS : 0
    };
    /**
     * Enum for operations used by the clientUpdatesQueue array.
     */
    var OperationEnum = {
        CREATE : 0,
        UPDATE : 1,
        DELETE : 2
    };
    /**
     * Initializes the client updates queue
     */
    (function initClientUpdatesQueue(){
        clientUpdatesQueue = angular.fromJson(localStorage.clientUpdatesQueue || "[]");
        if(localStorage.products === undefined){
            //GetAllFromServer();
        }
        clientUpdatesQueue = angular.fromJson(localStorage.clientUpdatesQueue || "[]");
    })();
    ProductService.$inject = ['$http', '$rootScope'];
    function ProductService($http, $rootScope) {
        /**
         * TODO
         * Will write a function that sends the offline operations updates to the server
         *
         * service.SendUpdates = SendUpdates;
        */
        var service = {};
        service.GetAllFromServer = GetAllFromServer;
        service.GetAll = GetAll;
        service.Get = Get;
        service.Create = Create;
        service.Update = Update;
        service.Delete = Delete;
        service.Synchronize = Synchronize;
        return service;

        /***************SYNCHRONIZATION TASKS***************
         ***************************************************/
        /**
         * Synchronize
         * Iterates through the pending updates queue and performs the corresponding call to the server
         */
        function Synchronize(){
            for (var key in clientUpdatesQueue) {
                switch(clientUpdatesQueue[key].Operation){
                    case 0:
                        CreateOnServer(clientUpdatesQueue[key].Record);
                        break;
                    case 1:
                        UpdateOnServer(clientUpdatesQueue[key].Record);
                        break;
                    case 2:
                        DeleteOnServer(clientUpdatesQueue[key].Record);
                        break;
                }
                clientUpdatesQueue.splice(key, 1);
            }
            updateLocalStorage();
        }
        /**
         * updateLocalStorage
         * Updates local storage with the lastest operations.
         */
        function updateLocalStorage(){
            localStorage.products = angular.toJson(products || "[]");
            localStorage.clientUpdatesQueue = angular.toJson(clientUpdatesQueue || "[]");
        }
        /**
         * GetAllFromServer
         * Gets all products matching the current session from the server and store them on the local storage.
         */
        function GetAllFromServer(){
            var session = angular.fromJson(localStorage.session);
            $http({
                method: 'GET',
                url: $rootScope.apiURL+'getAllClientProducts/'+session,
                headers: {'Content-Type': 'application/x-www-form-urlencoded'}
            }).success(function(response){
                if(response.ErrorMessage === null && response.Result !== null){
                    localStorage.products = angular.toJson(Object.keys(response.Result).map(function (key) {return response.Result[key]}));
                }else if(response.ErrorMessage !==null){
                    alert(response.ErrorMesage);
                }
            })
            .error(function(response){
                alert('Something went wrong. Please try again: '+response);
            });
        }
        /***************LOCAL TASKS*************************
         ***************************************************/
        /**
         * GetAll
         * Gets all the products from the local storage
         *
         * @return {Array} products
         */
        function GetAll(){
            if(localStorage.products !== undefined) {
                return angular.fromJson(localStorage.products);
            }else{
                GetAllFromServer();
            }
        }
        /**
         * Gets the specified product by its primary key
         *
         * @param {String} InvProductId
         * @return {Object} product
         */
        function Get(id){
            products = GetAll();
            var thisProduct = products.filter(function(p){
                return p.InvProductId === id;
            });
            updateLocalStorage();
            return thisProduct[0];
        }
        /**
         * Creates a product
         *
         * @param {Object} product
         */
        function Create(product){
            var result = true;
            if(!ValidateSnapshot(product)){
                return false;
            }
            products = GetAll();
            products.push(product);
            clientUpdatesQueue.push({'Section':SectionEnum.PRODUCTS, 'Operation':OperationEnum.CREATE, 'Record':product, 'Timestamp':Date.now()});
            updateLocalStorage();
            return result;
        }
        /**
         * Updates a product
         *
         * @param {Object} product
         */
        function Update(product){
            var result = true;
            if(!ValidateSnapshot(product)){
                return false;
            }
            products = GetAll();
            for (var key in products) {
                if(products[key].InvProductId === product.InvProductId){
                    products[key] = product;
                }
            }
            clientUpdatesQueue.push({'Section':SectionEnum.PRODUCTS, 'Operation':OperationEnum.UPDATE, 'Record':product, 'Timestamp':Date.now()});
            updateLocalStorage();
            return result;
        }
        /**
         * Deletes a product
         *
         * @param {Object} product
         */
        function Delete(product){
            var result = true;
            products = GetAll();
            for (var key in products) {
                if(products[key].InvProductId === product.InvProductId){
                    products.splice(key, 1);
                }
            }
            clientUpdatesQueue.push({'Section':SectionEnum.PRODUCTS, 'Operation':OperationEnum.DELETE, 'Record':product, 'Timestamp':Date.now()});
            updateLocalStorage();
            return result;
        }
        /***************SERVER COMMUNICATION****************
         ***************************************************/
        /**
         * Creates a product on the server
         *
         * @param {Object} product
         */
        function CreateOnServer(product){
            var session = angular.fromJson(localStorage.session);
            $http({
                method: 'POST',
                url: $rootScope.apiURL+'createProduct/'+session,
                data: $.param(product),
                headers: {'Content-Type': 'application/x-www-form-urlencoded'}
            }).success(function(response){
                if(response.ErrorMessage === null && response.Result !== null){
                    mixpanel.track("Product successfuly created at server: " + response.Result.InvProductId);
                }
            })
            .error(function(data){
                mixpanel.track("Create Product Went Wrong: "+data);
                alert('Something went wrong with product creation: '+data);
            });
        }
        /**
         * Updates a product on the server
         *
         * @param {Object} product
         */
        function UpdateOnServer(product){
            var session = angular.fromJson(localStorage.session);
            $http({
                method: 'POST',
                url: $rootScope.apiURL+'updateProduct/'+session,
                data: $.param(product),
                headers: {'Content-Type': 'application/x-www-form-urlencoded'}
            }).success(function(response){
                if(response.ErrorMessage === null && response.Result !== null){
                    mixpanel.track("Product successfuly edited: " + response.Result.InvProductId);
                }
            })
            .error(function(data){
                mixpanel.track("Create Product Went Wrong: "+data);
                alert('Something went wrong with product creation: '+data);
            });
        }
        /**
         * TODO
         * Deletes a product on the server
         *
         * @param {Object} product
         */
        function DeleteOnServer(product){
            return true;
        }
        /***************VALIDATION UTILITIES****************
         ***************************************************/
        function ValidateSnapshot(product){
            var result = true;
            if(product === null || product === undefined){
                return false;
            }
            if(!product.ApplicableTaxKeys.split(',') instanceof Array || product.ApplicableTaxKeys !== null){
                return false;
            }
            if(product.Barcode.length < 1 || product.Barcode === null || product.Barcode === undefined){
                return false;
            }
            if(product.CliClientId.length !== 10){
                return false;
            }
            if(product.Description.length < 1 || product.Description === null || product.Description === undefined){
                return false;
            }
            if(product.InternalCode.length < 1 || product.InternalCode === null || product.InternalCode === undefined){
                return false;
            }
            if(!product.InvProductCategoryId.split(',') instanceof Array || product.InvProductCategoryId !== null){
                return false;
            }
            if(product.Name.length < 1 || product.Name === null || product.Name === undefined){
                return false;
            }
            if(product.SaleUnitType.length < 1 || product.SaleUnitType === null || product.SaleUnitType === undefined){
                return false;
            }
            if(product.Status.length < 1 || product.Status === null || product.Status === undefined){
                return false;
            }
            if(product.UnitMeasure.length < 1 || product.UnitMeasure === null || product.UnitMeasure === undefined){
                return false;
            }
            return result;
        }
    }
})();

When I run karma start on the terminal, I get these errors:

$ karma start 01 02 2016 18:41:39.939:WARN [karma]: No captured browser, open http://localhost:9876/ 01 02 2016 18:41:39.948:INFO [karma]: Karma v0.13.19 server started at http://localhost:9876/ 01 02 2016 18:41:39.953:INFO [launcher]: Starting browser PhantomJS 01 02 2016 18:41:40.203:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket /#yTkMYAHDCJhxIwALAAAA with id 3944707 PhantomJS 2.1.1 (Linux 0.0.0) factory: ProductService encountered a declaration exception FAILED Error: [$injector:unpr] http://errors.angularjs.org/1.3.7/$injector/unpr?p0=ProductServiceProvider%20%3C-%20ProductService (line 38) /home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js:38:332 [email protected]/home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js:36:309 /home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js:38:384 [email protected]/home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js:36:309 [email protected]/home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js:37:65 [email protected]/home/eduardo/ventamia/vm2/clientside/www-app/node_modules/angular-mocks/angular-mocks.js:2517:26 undefined SyntaxError: JSON Parse error: Expected '}' in /home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js (line 14) [email protected][native code] o[email protected]/home/eduardo/ventamia/vm2/clientside/www-app/js/angular/angular.min.js:14:161 /home/eduardo/ventamia/vm2/clientside/www-app/js/app-services-tests/product.service.test.js:7:47 global [email protected]/home/eduardo/ventamia/vm2/clientside/www-app/js/app-services-tests/product.service.test.js:1:9 PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.041 secs / 0.008 secs) 01 02 2016 18:48:37.489:INFO [watcher]: Changed file "/home/eduardo/ventamia/vm2/clientside/www-app/js/app-services-tests/product.service.test.js". PhantomJS 2.1.1 (Linux 0.0.0): Executed 0 of 0 ERROR (0.04 secs / 0 secs) ^[email protected]:~/ventamia/vm2/clientside/www-app/js$ karma start 01 02 2016 18:48:42.020:WARN [karma]: No captured browser, open http://localhost:9876/ 01 02 2016 18:48:[email protected]:~/ventamia/vm2/clientside/www-app/js$ karma start 01 02 2016 18:48:42.020:WARN [karma]: No captured browser, open http://localhost:9876/ 01 02 2016 18:48:42.029:INFO [karma]: Karma v0.13.19 server started at http://localhost:9876/ 01 02 2016 18:48:42.034:INFO [launcher]: Starting browser PhantomJS 01 02 2016 18:48:42.277:INFO [PhantomJS 2.1.1 (Linux 0.0.0)]: Connected on socket /#9-zdfLe9rv-EOnGvAAAA with id 57338630 PhantomJS 2.1.1 (Linux 0.0.0): Executed 0 of 0 ERROR (0.037 secs / 0 secs)

Thanks in advance.

Ad

Answer

Angular is giving you an error message in the form of this URL:

https://docs.angularjs.org/error/$injector/unpr?p0=ProductServiceProvider%20%3C-%20ProductService

It saying it doesn't know how to find your ProductService.

In your factory, you're declaring the ProductService to be in the module named inspinia, so you need to load this module before your tests run.

Add this before your existing beforeEach:

beforeEach(module('inspinia'));

Then Angular should find the service and be able to inject it.

PS: You have some duplicate code in the test, you can remove the second occurrence of the var ProductService; and beforeEach and just use the outer one.

Ad
source: stackoverflow.com
Ad