Spying on Methods of Object Under Test Vs. Spying on Methods of It's Dependencies (in Angul...

Datetime:2016-08-23 02:35:10          Topic: AngularJS  Test Engineer           Share

In this post I quickly want to go over the difference between using spyOn for methods of the object under tests and using spyOn to spy on methods of an object that is a dependency of the object under test. This is another post that's all about "testing one thing at a time" in your AngularJS unit tests by "using the real thing" for the object or method under test and using spyOn to fake rest. 

Use the real thing for the object / method under test,

and use spyOn to fake rest.

Make a New Unit Test File

Sanity check- here's where we're starting. This is a file containing an empty suit of unit tests which just sets up out Angular module and injects one service, ScrollCalulator. I'm still using Es5 JavaScript, but the main ideas will apply for really any language.  

(function() {
  'use strict';

  describe('controllers', function(){
    var AgendaAutoScroller;

    beforeEach(module('agendaIpad'))
    beforeEach(inject(function(_AgendaAutoScroller_) {
      AgendaAutoScroller = _AgendaAutoScroller_;

      });
    })





  });

})();

Using spyOn To Make Fake Implementations for Methods on the Object Under Test

Suppose we're testing the "calculateScrollAmount" method, but for some reason we want to define a fake implementation for "getContainerHeight" so that it just automatically returns 500. In this code snippet below I'm spying on getContainerHeight , another method in AgendaAutoScroller and defining a fake implementation for it that just returns 500. The point of doing this is that the calculateScroll method calls 'getContainerHeight' at some point when it's invoked so in order for the assertion that calls "calculateScroll" you need to have getContainerHeight returning something valid (like 500). 

(function() {
  'use strict';

  describe('AgendaAutoScroller', function(){
    var AgendaAutoScroller;

    beforeEach(module('agendaIpad'))
    beforeEach(inject(function(_AgendaAutoScroller_) {
      AgendaAutoScroller = _AgendaAutoScroller_;

      spyOn(AgendaAutoScroller,['getContainerHeight']).and.callFake(function () {
        return 500;
      });

      });
    })


    it('should calculate 800 when I enter 400', function () {
        
      expect(AgendaAutoScroller.calculateScroll(400)).toEqual(800);

    });



  });

})();

Using spyOn for Methods of Dependency Objects

You can also spyOn methods that are calling from dependencies within the function you are trying to test. Suppose the instead of 'getContainerHeight' being a method on AgendaAutoScroller, it's instead a method of a different AngularJS "service service", let's call this new one HeightTracker.  So in our AgendaAutoScroller service when it's created we inject HeightTracker, and we later call HeightTracker.getcontainerHeight() from inside of AgendaAutoScroller.calculateScroll().

So in our unit test we can inject this dependency, and just spy on it's methods. Because they are the exact same name, Angular knows to use this "spied on" version of the HeightTracker dependency when creating AgendaAutoScroller.

(function() {
  'use strict';

  describe('AgendaAutoScroller', function(){
    var AgendaAutoScroller;
    var HeightTracker;

    beforeEach(module('agendaIpad'))
    beforeEach(inject(function(_AgendaAutoScroller_), _HeightTracker_ {
      AgendaAutoScroller = _AgendaAutoScroller_;
      HeightTracker = _HeightTracker_;

      spyOn(HeightTracker,['getContainerHeight']).and.callFake(function () {
        return 500;
      });

      });
    })


    it('should calculate 800 when I enter 400', function () {
        
      expect(AgendaAutoScroller.calculateScroll(400)).toEqual(800);

    });



  });

})();

Bonus Tip: "Spy on" Variables

The actual Jasmine method  spyOn won't work for variables, but in therms of being able to create your own fake implementation / value for unit tests in the same section of your file when you would spyOn your methods you can just manually set variables of either your object under test or any dependencies simply by injecting them and setting the values. In the example below, when AgendaAutoScroller is created and has  HeightTracker injected into it the value of HeightTracker.initialValue is true.

(function() {
  'use strict';

  describe('AgendaAutoScroller', function(){
    var AgendaAutoScroller;
    var HeightTracker;

    beforeEach(module('agendaIpad'))
    beforeEach(inject(function(_AgendaAutoScroller_), _HeightTracker_ {
      AgendaAutoScroller = _AgendaAutoScroller_;
      HeightTracker = _HeightTracker_;

      HeightTracker.initialValue = true;

      });
    })


    it('should calculate 800 when I enter 400', function () {
        
      expect(AgendaAutoScroller.calculateScroll(400)).toEqual(800);

    });



  });

})();




About List