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();
}