Sarah
Sarah

Reputation: 2003

Using Jasmine spyOn with a method of an object defined in a factory

In my angular JS application I have a mainController which takes in a userFactory as a parameter. The userFactory consists of an object called userService which in turn has a userDetails object and some methods including resetUserDetails. (see further down)

In the mainController I have a logOut function which calls the userFactory.userService.resetUserDetails method. I would like to test this logOut function with jasmine however I am getting some errors. I am very new to Jasmine so apologies if its something obvious i'm missing.

So firstly in my Jasmine suite I create a MainControllerSpec for testing my mainController.

Within this spec I'm injecting a factory called userFactory. I'm trying to spyOn my resetUserDetails method as follows however getting an error:

spyOn(userFactory, 'userService.resetUserDetails');

Error: userService.resetUserDetails() does not exist.

I tried this process by creating a function called test in my userFactory (outside the userService object) and it works well so at least I know the factory injection in the spec is set up fine.
Any help greatly appreciated. Thanks

MainControllerSpec.js

describe("MainController", function () { 
    beforeEach(angular.mock.module('mapModule', 'ngRoute','ngTouch', 'ngAnimate'));
    var scope, userFactory; 

    beforeEach(inject(function($rootScope, $controller, _userFactory_){
        scope = $rootScope.$new();
        userFactory = _userFactory_;
        $controller('mainController', {
            $scope: scope
        }); 
    }));


   describe('The logOut function', function() {
        it('should call the resetUserDetails function of the userFactory.userService object and reset the userDetails object', function() {
            //spyOn takes in a factory and a method of that factory 
            spyOn(userFactory, 'userService.resetUserDetails');
            //spyOn(userFactory, 'test'); tried this and it works.
            scope.logOut();
            expect(userFactory.userService.resetUserDetails).toHaveBeenCalled();  
        });
    });

});

logOut function in mainController

   $scope.logOut = function(){
         userFactory.userService.resetUserDetails(); 
         //userFactory.test(); //tried this with spyOn in jasmine
    }

userFactory

mapApp.factory('userFactory', function(){

    var userService = {
        /*
         * Initialize a userDetails object. 
         */
        userDetails : {   
            "userID" : null,
            "facebookUserID" : "",
            "facebookName" : "",
            "facebookProfilePic" : "",
            "userPrivilegeID" : 1,
            "userToken" : "",
            "isLoggedIn" : false
        },
        resetUserDetails : function(){
            /*
             * This method resets the userDetails object.
             */
            this.userDetails = {
                "userID" : null,
                "facebookUserID" : "",
                "facebookName" : "",
                "facebookProfilePic" : "",
                "userPrivilegeID" : 1,
                "userToken" : "",
               "isLoggedIn" : false
            };
        }
    }; 
    var test = function(){
        /*
        * for testing spyOn in Jasmine
        */
    };
    //return public API so that we can access it in all controllers
    return{
      userService: userService,
      test: test
    };
});

Upvotes: 1

Views: 3824

Answers (1)

quirimmo
quirimmo

Reputation: 9988

You need to mock your userFactory before to inject it directly. Goals of unit tests are to test the files as black boxes, without testing also the logic of the related methods directly.

For them you will write your spec file for the userFactory instead.

In this case what you can do is something like the following:

describe("MainController", function() {

  beforeEach(angular.mock.module('mapModule', 'ngRoute', 'ngTouch', 'ngAnimate'));
  var scope, userFactory;

  // here mock the methods of your factory
  beforeEach(module(function($provide) {
    $provide.value('userFactory', {
      myFirstObject: {
        myFirstMethod: function() {}
      }
    });
  }));

  beforeEach(inject(function($rootScope, $controller, _userFactory_) {
    scope = $rootScope.$new();
    userFactory = _userFactory_;
    $controller('mainController', {
      $scope: scope
    });
  }));


  describe('The logOut function', function() {
    it('should call the resetUserDetails function of the userFactory.userService object and reset the userDetails object', function() {
      //here spy on the method and return what you would like to return in this test
      // or if you don't need to manage the return, as it seems you don't, just use callThrough
      spyOn(userFactory.myFirstObject, 'myFirstMethod').and.callThrough();
      scope.logOut();
      expect(userFactory.myFirstObject.myFirstMethod).toHaveBeenCalled();
    });
  });

});

Upvotes: 1

Related Questions