Spies: spyOn(), and.callThrough(), and.returnValue(), and.callFake()

Test doubles like mocks, spies, and stubs are integral part of unit testing. In Jasmine, there are few such functions which can stub any function and track calls to it and all its arguments. They are known as spies.

Consider the below constructor function

				
				function Season() {
				  this.season = 'Spring';
				  this.nextSeason = function() {
				    this.season = 'Summer';
				    return this.season;
				  },
				  this.getNextSeason = function() {
				    return this.nextSeason();
				  }
				};	
				
			

Inside the following spec, we first create an instance of Season() and invoke the getNextSeason() method in the next line.

season bars Season Bars by NASA. Public Domain

On call of the getNextSeason() method, the nextSeason() method also gets called, and hence the value of season gets assigned to "Summer" and the spec passes.

				
				describe('spyOn() Demo. Season', function() {
				    it('should be Summer', function() {
				        var s = new Season();
				        s.getNextSeason();
				        expect(s.season).toEqual('Summer');
				    });
				});
				
			
jasmine spyon success
jasmine spyonThe above spy icon is by Freepik from https://www.flaticon.com

But in unit testing, while testing a particular method/function we may not always want to call through all the other dependent methods/functions (it makes sense when you want to avoid actual AJAX calls which demand some delay time for the response). Take for example the s object created in the above spec. We may just want to test its getNextSeason() method without actually calling the nextSeason() method. Jasmine provides the spyOn() function for such purposes. spyOn() takes two parameters: the first parameter is the name of the object and the second parameter is the name of the method to be spied upon. It replaces the spied method with a stub, and does not actually execute the real method. The spyOn() function can however be called only on existing methods.

In the spec below, we spy on the nextSeason() method just before the getNextSeason() call

				
				describe('spyOn() Demo. Season', function() {
				    it('should be Summer', function() {
				        var s = new Season();
				        spyOn(s, 'nextSeason');
				        s.getNextSeason();
				        expect(s.season).toEqual('Summer');
				    });
				});
				
			

Now since the nextSeason() method is spied on, calling the getNextSeason() method does not call the actual nextSeason() method as in the first case, and hence the value of season is not changed. The spec fails.

and.callThrough()

To make the spy call the actual method, we chain and.callThrough() to the spyOn() function as illustrated in the below spec

					
					describe('spyOn() Demo. Season', function() {
					    it('should be Summer', function() {
					        var s = new Season();
					        spyOn(s, 'nextSeason').and.callThrough();
					        s.getNextSeason();
					        expect(s.season).toEqual('Summer');
					    });
					});
					
				

and the season property is changed to "Summer". The spec passes.

In Sinon, a spy calls through the method it is spying on. So, sinon.spy(s,'nextSeason'); in Sinon is equivalent to spyOn(s,'nextSeason').and.callThrough(); in Jasmine.

and.returnValue()

A spy can be made to return a preset/fixed value (without the need for calling the actual methods using and.callThrough()). This can be achieved by chaining the spyOn() function with and.returnValue().

					
					describe('spyOn() Demo. Season', function() {
					    it('should be Autumn', function() {
					        var s = new Season();
					        spyOn(s, 'nextSeason').and.returnValue('Autumn');
					        s.getNextSeason();
					        expect(s.nextSeason()).toEqual('Autumn');
					    });
					});
					
				

In the above spec, if the nextSeason() method is not spied upon, the actual value passed into expect() would have been just "Summer". And the spec would have failed.

and.callFake()

A spy can do more than just return a fixed value, it can also replace an entire spied function using and.callFake()

					
					describe('spyOn() Demo. Season', function() {
					    it('should not be Summer', function() {
					        var s = new Season();
					        spyOn(s, 'nextSeason').and.callFake(function(){
					          console.log('in the future');
					          return 'Winter';
					        });
					        s.getNextSeason();
					        expect(s.nextSeason()).not.toEqual('Summer');
					    });
					});
					
				

jasmine spyon callfake success

Arguments can also be passed into and.callFake()

					
					describe('spyOn() Demo. Season', function() {
					    it('should be Winter in Narnia', function() {
					        var s = new Season();
					        spyOn(s, 'nextSeason').and.callFake(function(place){
					          return 'Winter in ' + place;
					        });
					        s.getNextSeason();
					        expect(s.nextSeason('Narnia')).toEqual('Winter in Narnia');
					    });
					});
					
				

jasmine spyon callfake passing arguments

Notes

  • The spyOn() function in Jasmine is also similar to the stub() function in jstest, another JavaScript test framework.