jasmine spies on inherited methods (with typescript) not working as expected with toHaveBeenCalled() jasmine spies on inherited methods (with typescript) not working as expected with toHaveBeenCalled() typescript typescript

jasmine spies on inherited methods (with typescript) not working as expected with toHaveBeenCalled()


You have to understand the mechanics behind Typescript and the spying.

First on Typescript ...

I'm ignoring the extra parens in class Parent().

Typescript uses prototypal inheritance behind the curtain. Thus a prototype will copy the referenced properties from the "base class" to the new class. This is what the for loop does in the __extends() function.

This is the ES5 code your Typescript is translated to:

var __extends = (this && this.__extends) || function (d, b) {    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];    function __() { this.constructor = d; }    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());};var Parent = (function () {    function Parent() {    }    Parent.prototype.buyFood = function () {        // buy food    };    return Parent;}());var Husband = (function (_super) {    __extends(Husband, _super);    function Husband() {        return _super.apply(this, arguments) || this;    }    Husband.prototype.makeDinner = function () {        _super.prototype.buyFood.call(this);        // make dinner;    };    return Husband;}(Parent));

You can translate typescript using this Typescript playground.

Your super expression calls the buyFood() method of the parent class and not the method of the "inherited" Husband.

See the line

_super.prototype.buyFood.call(this);

and follow the _super reference.

Now Jasmine Spies ...

A spy will replace the named function of the passed object by a spy function that will act as a proxy. That proxy can now track calls and, depending on the programmed behavior, control whether to call the original function, a fake, return a value or do nothing (default).

A very simplified spyOn() could look like this:

function spyOn(obj, fn) {    var origFn = obj[fn],        spy = function() {            spy.calls.push(arguments);        };    spy.calls = [];    obj[fn] = spy;}

The actual spy method is much more complex though.

Your line

spyOn(husband, 'buyFood');

will actually replace the method in the instance of Husband by a spy. But, since the code calls the reference of the base class (the parent prototype) it's not the same function that you've just replaced.

Solution

You should either call the this referenced method

class Husband extends Parent {    makeDinner() {        // call byFood() via this        this.buyFood();    }}

... or spy on the parent prototype (super):

it('Should make a good dinner', () => {    spyOn(Parent.prototype, 'buyFood');    husband.makeDinner();    expect(Parent.prototype.buyFood).toHaveBeenCalled();}


When using ES6, the Parent.prototype will not work. Use Object.getPrototypeOf instead.

This is what worked for me:

it('Should make a good dinner', () => {    spyOn(Object.getPrototypeOf(Object.getPrototypeOf(husband), 'buyFood');    husband.makeDinner();    expect(Parent.prototype.buyFood).toHaveBeenCalled();}


const spy = spyOn(husband, 'buyFood');husband.makeDinner();expect(spy).toHaveBeenCalled();