Angular unit-test controllers - mocking service inside controller
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)
Related videos on Youtube
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, 2020Comments
-
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 over 10 yearsYour
controller.js
syntax is wrong -
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 over 10 yearsI refuse to help until you put that strict statement inside a function: pbs.twimg.com/media/BcMF93NCQAAXKhW.jpg
-
-
Jackie over 9 yearsWhat if I just wanted to make sure the fn function was called once?
-
Jesus Rodriguez over 9 years@Jackie the spy has
.calls.count()
you can use to verify it is 1.