I'm currently trying to unit test my angular service using SinonJS, but have been running into an issue and was hoping someone could possibly shed some light on why this is occurring. I have reconstructed my current project to illustrate the problem at hand.
I have also provided a DEMO
I have a service, peopleService
:
(function (){
angular.module('myApp')
.factory('peopleService', peopleService);
peopleService.$inject = ['$q'];
function peopleService ($q){
var people = ['Homer', 'Marge', 'Bart', 'Lisa', 'Maggie'];
// in actual project, this makes an http request
function getFamily () {
return people;
}
function getAdults (){
var family = getFamily();
return family.filter(function (person){
return person === 'Homer' || person === 'Marge';
});
}
return {
getFamily: getFamily,
getAdults: getAdults
}
}
}());
In this service, my method getAdults
uses getFamily
, filters the results, and returns the data.
In my unit test, I am trying to mock getFamily
and see if that method is being called. Now this is where the problem presents itself...
First thing I tried was stubbing out the method and overwriting the current method, like so:
beforeEach(function (){
module('myApp');
inject(function (_peopleService_){
peopleService = _peopleService_; // get the service
sinon.stub(peopleService, 'getFamily'); // stub it
});
});
I then go to test whether getAdults
calls the getFamily
method:
it('getAdults should call "getFamily" once', function(){
peopleService.getAdults();
expect(peopleService.getFamily.calledOnce).toBe(true);
});
The test fails and the stubbed method is not called...
I debug and find out that although the function has in fact changed:
The service still holds a reference (closure) to what the method used to be when the service was created:
My initial thought was I didn't stub the method correctly. I then attempted overwriting the method using $provide
($provide.value
) as well as $injector decorator
and I ended up getting the same result (closure held onto the original method).
A solution to this would be to use this
:
function getAdults (){
var family = this.getFamily(); // <-- by using this.getFamily would reference the mock
return family.filter(function (person){
return person === 'Homer' || person === 'Marge';
});
}
However, I do not understand why I have to do this.
In short, does anyone know:
this
Thank you so much for your time.
When you stub a method on an object, the property of that object is overriden, not the original function that it references.
Take, for example, this code:
function myFunction () {};
var myObj = { prop: myFunction };
myObj.prop === myFunction; // true
myObj.prop = 'changed';
typeof myFunction === 'function'; // true
myObj.prop === myFunction; // false
Changing myObj.prop
did not change the original function, myFunction
still exists in its own right. myObj.prop
, however, has lost its reference to myFunction
. If this was in sinon world, stubbing simply changed the reference of myObj.prop
to a stub object.
This is why when testing code in a service which calls another function in the same service, that code needs to reference the same object returned by the service. If you want to avoid using the this
keyword everywhere, you can structure your service like so:
angular.module('myApp')
.factory('peopleService', peopleService);
peopleService.$inject = ['$q'];
function peopleService ($q){
var service = {
getFamily: getFamily,
getAdults: getAdults
};
return service;
function getFamily () {
// ...
}
function getAdults (){
var family = service.getFamily(); // <-- reference service.getFamily()
// ...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With