I have an angular service. Inside this service I have an object with a function, that references another function on the service. (Code below)
I want to use Jasmine (1.3) to spy on my service function, to verify that when the object's function gets called, it actually calls the real function.
My problem: After calling spyOn, the real function is still being called.
FooService.js
angular.module('foo').service("FooService", function() {
var self = this;
this.fooFunction = function() {
console.log("Foo function is being called");
}
this.bar = {
barFunction : self.fooFunction
}
});
FooService-spec.js
describe("Testing FooService", function() {
var service;
beforeEach(inject(function(_FooService_) {
service = _FooService_;
}));
describe("Test bar object", function() {
it("should call fooFunction when bar.barFunction is called", function() {
spyOn(service, "fooFunction");
service.bar.barFunction();
expect(service.fooFunction).toHaveBeenCalled();
});
});
});
I have found that if I change FooServce.js to the following, this all works though:
FooService - Working
angular.module('foo').service("FooService", function() {
var self = this;
this.fooFunction = function() {
console.log("Real function is being called");
}
this.bar = {
barFunction : function() {
return self.fooFunction();
}
}
});
What part of JavaScript / Angular / Jasmine am I failing to understand in the first example?
spyOn
operates by replacing the value of an object property with a different value. When you do spyOn(service, "fooFunction");
you're doing something like
var realFunc = service.fooFunction;
service.fooFunction = function() {
doSpyStuff();
return realFunc.apply(this, arguments);
}
Note that this doesn't modify the value service.fooFunction
. It actually modifies service
-- namely, one of the properties of service
is now a totally different function. This replacement will only affect the fooFunction
property of service
. If you're not accessing a property of service
, you're certainly not on your way to invoking the spy function.
So let's apply this knowledge to your case. In your test, you are accessing a property of service.bar
. While service.bar.barFunction
and service.fooFunction
were originally the same value, service
has had its fooFunction
property replaced by the spy, while (very importantly) service.bar
has not had any of its properties mutated by spyOn
. When you call service.bar.barFunction()
, you directly invoke the real function, and have no connection to the spy that lives on service
's fooFunction
property.
By contrast, when you do barFunction: function() { return self.fooFunction(); }
as an anonymous function, you actually are accessing the spy property value on service
, because here self
happens to be service
, so self.fooFunction
is service.fooFunction
, which is the property that holds a spy replacement value.