Angular unit-test controllers - mocking service inside controller

27,525

There are two ways (or more for sure).

Imagining this kind of service (doesn't matter if it is a factory):

app.service('foo', function() {
  this.fn = function() {
    return "Foo";
  };
});

With this controller:

app.controller('MainCtrl', function($scope, foo) {
  $scope.bar = foo.fn();
});

One way is just creating an object with the methods you will use and spy them:

foo = {
  fn: function() {}
};

spyOn(foo, 'fn').andReturn("Foo");

Then you pass that foo as a dep to the controller. No need to inject the service. That will work.

The other way is to mock the service and inject the mocked one:

beforeEach(module('app', function($provide) {
  var foo = {
    fn: function() {}
  };
  
  spyOn(foo, 'fn').andReturn('Foo');
  $provide.value('foo', foo);
}));

When you inject then foo it will inject this one.

See it here: http://plnkr.co/edit/WvUIrtqMDvy1nMtCYAfo?p=preview

Jasmine 2.0:

For those that struggle with making the answer work,

as of Jasmine 2.0 andReturn() became and.returnValue()

So for example in the 1st test from the plunker above:

describe('controller: MainCtrl', function() {
  var ctrl, foo, $scope;

  beforeEach(module('app'));
  
  beforeEach(inject(function($rootScope, $controller) {
    foo = {
      fn: function() {}
    };
    
    spyOn(foo, 'fn').and.returnValue("Foo"); // <----------- HERE
    
    $scope = $rootScope.$new();
    
    ctrl = $controller('MainCtrl', {$scope: $scope , foo: foo });
  }));
  
  it('Should call foo fn', function() {
    expect($scope.bar).toBe('Foo');
  });

});

(Source: Rvandersteen)

Share:
27,525

Related videos on Youtube

Liad Livnat
Author by

Liad Livnat

Awesome client side / server side / mobile (HTML5) / facebook developer. Web Client: AngularJS, Backbone.js, Ember.js, Javascript(JQuery), HTML5, CSS3. Server: PHP - cake, CodeIgniter, Symfony, Laravel, Ruby , MVC 4.0, C# and ASP.NET 4.0 Database: MongoDB, mySQL, MSSQL ,Azure, Google Cloud : Azure, Amazon EC2, engineyard, Rackspace Source Controls: TFS, SVN, BitBucket, Git http://il.linkedin.com/in/liadl/ http://liadlivnat.com

Updated on July 22, 2020

Comments

  • Liad Livnat
    Liad Livnat almost 4 years

    I have the following situation:

    controller.js

    controller('PublishersCtrl',['$scope','APIService','$timeout', function($scope,APIService,$timeout) {
    
        APIService.get_publisher_list().then(function(data){
    
                });
     }));
    

    controllerSpec.js

    'use strict';
    
    describe('controllers', function(){
        var scope, ctrl, timeout;
        beforeEach(module('controllers'));
        beforeEach(inject(function($rootScope, $controller) {
            scope = $rootScope.$new(); // this is what you missed out
            timeout = {};
            controller = $controller('PublishersCtrl', {
                $scope: scope,
                APIService: APIService,
                $timeout: timeout
            });
        }));
    
        it('should have scope variable equals number', function() {
          expect(scope.number).toBe(3);
        });
    });
    

    Error:

     TypeError: Object #<Object> has no method 'get_publisher_list'
    

    I also tried something like this, and it didn't work:

    describe('controllers', function(){
        var scope, ctrl, timeout,APIService;
        beforeEach(module('controllers'));
    
        beforeEach(module(function($provide) {
        var service = { 
            get_publisher_list: function () {
               return true;
            }
        };
    
        $provide.value('APIService', service);
        }));
    
        beforeEach(inject(function($rootScope, $controller) {
            scope = $rootScope.$new(); 
            timeout = {};
            controller = $controller('PublishersCtrl', {
                $scope: scope,
                APIService: APIService,
                $timeout: timeout
            }
            );
        }));
    
        it('should have scope variable equals number', function() {
          spyOn(service, 'APIService');
          scope.get_publisher_list();
          expect(scope.number).toBe(3);
        });
    });
    

    How can i solve this? any suggestions?

    • dcodesmith
      dcodesmith over 10 years
      Your controller.js syntax is wrong
    • Liad Livnat
      Liad Livnat over 10 years
      @dcodesmith yes i shorten it cause what really matters is the function call, ignore all the rest APIService.get_publisher_list()
    • Michael J. Calkins
      Michael J. Calkins over 10 years
      I refuse to help until you put that strict statement inside a function: pbs.twimg.com/media/BcMF93NCQAAXKhW.jpg
  • Jackie
    Jackie over 9 years
    What if I just wanted to make sure the fn function was called once?
  • Jesus Rodriguez
    Jesus Rodriguez over 9 years
    @Jackie the spy has .calls.count() you can use to verify it is 1.