Search code examples
javascriptangularjsunit-testingjasminerestangular

jasmine test case for Restangular within a function


How to write jasmine test for Rectangular for a scenario like as shown below

script

$scope.allEmployees = [{
            visible: true,
            empId: "EMP148"
 }];

$scope.employeeDocuments = {  "EMP148": [{
                                        "empId": "EMP148",
                                        "department": "Sales",
                                        "firstName": "Manu",
                                        "lastName": "Mathew",
                                        "place": "Kolkata"
                                        }]
                           };

var employeesCopy = angular.copy($scope.allEmployees);
$scope.allEmployees.push({visible: true, empId: "EMP489"});  

$scope.addEmployees = function (employeesCopy) {
    var newEmployee = {visible: true, empId: "EMP489"};
    var emplyeeList = {
        employees: $scope.allEmployees,
        newEmployee: newEmployee
    };
    Restangular.all('emp/getEmployees').post(emplyeeList).then(function (employees) {
        if (!_.isEmpty(employees[emplyeeList.newEmployee.empId])) {
            if ($scope.employeeDocuments.hasOwnProperty(emplyeeList.newEmployee.empId)) {
                delete $scope.employeeDocuments[emplyeeList.newEmployee.empId];
            }
            $scope.employeeDocuments[emplyeeList.newEmployee.empId] = employees[emplyeeList.newEmployee.empId];
            setMyEmployees(true);
            $scope.flag = true;
            console.log("success");
        } else {
            $scope.employeeDocuments = employeesCopy;
            console.log("no documents");
        }
        $scope.flag = false;
    }, function (error) {
        console.log("failed");
        $scope.flag = false;
    });
};

$scope.addEmployees(employeesCopy);
setMyEmployees = function (flag)
{
    // other implementation
};

I have wrote a test cases like as shown below, but i am getting exception like spyOn could not find an object to spy upon for all()

test case

WORKING DEMO - contains the full logic as well as the test cases

describe('Testing Controllers', function() {
    describe('Testing EmployeeController Controller', function() {
        var EmployeeController, $scope, $httpBackend, Restangular;

        beforeEach(function() {
           module('emp');
        }); 

        beforeEach(inject(function($controller, $rootScope, $filter, $injector, Restangular, $httpBackend) {
            $scope = $rootScope.$new();
            $httpBackend = $httpBackend;
            Restangular = $injector.get("Restangular");
            EmployeeController = $controller('EmployeeController ', {
                $rootScope: $rootScope,
                $scope: $scope,
                $filter: $filter
            });
        }));

        it('should add new employees when addEmployees() is called', inject(function($httpBackend)
        {
           $scope.allEmployees = [{
                                    visible: true,
                                    empId: "EMP148"
                              }];

           $scope.employeeDocuments = {  "EMP148": [{
                                            "empId": "EMP148",
                                            "department": "Sales",
                                            "firstName": "Manu",
                                            "lastName": "Mathew",
                                            "place": "Kolkata"
                                            }]
                                   };

           var employeesCopy = angular.copy($scope.allEmployees);

           spyOn(Restangular, 'all').andCallThrough();

           var newEmployee = {visible: true, empId: "EMP489"};
           var emplyeeList = {
                employees: $scope.allEmployees,
                newEmployee: newEmployee
           };

           var mockToReturn = {
                "EMP489": [{
                "empId": "EMP489",
                "department": "Sales",
                "firstName": "Ram",
                "lastName": "Mohan",
                "place": "Delhi"
                }]
            };
            $scope.addEmployees(employeesCopy);
            $httpBackend.expectPOST('emp/getEmployees',emplyeeList).respond(mockToReturn);
            expect(Restangular.all).toHaveBeenCalledWith('emp/getEmployees');
            expect(setMyEmployees(true)).toHaveBeenCalled();

        }));
    });
});

Solution

  • You have multiples problem :

    First, to answer your question to use toHavebeenCalled you have to create a spy first.

    What I would do is put setMyEmployees at the scope level, and then add a spy on the scope

    spyOn($scope);
    

    But you have a test that make an asyncronous request, so your test case will fail because it will reach the end before the asynchronous request succeed.

    With jasmine 2 you can use done() for asynchonous test :

    it('should add new employees when addEmployees() is called', function(done) 
    {
        //call when asyncronous operation is finish
        done();
    }
    

    But with the way you've created your function, you can't use done. You will have to have a callback block in your method or a promise

    Callback :

    $scope.addEmployees = function(employeesCopy, success, failure)
    {
        //code
        Restangular.all('user/getEmployees').post(emplyeeList).then(function(employees)
        {
            if (!_.isEmpty(employees[emplyeeList.employeeId]))
            {
               // code
            }
            else
            {
                // code
            }
            success();
            $scope.flag = false;
        }, function(error)
        {
            failure();
            $scope.flag = false;
        });
    };
    

    Note the toHaveBeenCall syntax

    it('should add new employees when addEmployees() is called', function(done) 
    {
    var employeesCopy = {
        firstName: "Manu",
        lastName: "Sam"
    };
    
    spyOn($scope);
    
    $scope.addEmployees(employeesCopy, function(){
    
        done();
    
    });
    
    expect($scope.setMyEmployees).toHaveBeenCalledWith(true);
    });
    

    I would prefer a promise syntax :

    $scope.addEmployees = function(employeesCopy, defer)
    {
        //code
        Restangular.all('user/getEmployees').post(emplyeeList).then(function(employees)
        {
            if (!_.isEmpty(employees[emplyeeList.employeeId]))
            {
               // code
            }
            else
            {
                // code
            }
            defer.resolve();
            $scope.flag = false;
        }, function(error)
        {
            defer.reject();
            $scope.flag = false;
        });
    };
    
    
    it('should add new employees when addEmployees() is called', function(done) 
    {
    var employeesCopy = {
        firstName: "Manu",
        lastName: "Sam"
    };
    
    spyOn($scope);
    
    var defer = $q.defer;
    defer.promise.then(function(){
    
        console.log("success");
        done();
    }, function (){
        done();
        console.log("error");
    });
    
    $scope.addEmployees(employeesCopy, defer);
    
    expect($scope.setMyEmployees).toHaveBeenCalledWith(true);
    });
    

    The last problem you will have is to mock the network call if you want your test to be truly unitary (else you'r also testing the backend answer, that could be what you want) If you want to mock the call you should look at $httpBackend, this blog post seems to have more informations : https://ath3nd.wordpress.com/2013/08/05/15/ (not mine)

    EDIT, add dependancy to test, before your it(), use a beforeEach :

    var myService, Restangular;
        beforeEach(function() {
            inject(function($injector) {
                myService = $injector.get('MyService');//exemple service
                Restangular = $injector.get("Restangular");
            });
        });
    

    EDIT 2 :

    Ok I havn't explain it correctly, try :

    beforeEach(inject(function($controller, $rootScope, $filter, $injector, _Restangular_, _$httpBackend_) {
            $scope = $rootScope.$new();
            $httpBackend = _$httpBackend_;
            Restangular = _Restangular_;
            EmployeeController = $controller('EmployeeController ', {
                $rootScope: $rootScope,
                $scope: $scope,
                $filter: $filter
            });
        }));