I'm currently having an issue when spying on inherited methods for calls in typescript classes, where the toHaveBeenCalled() method is returning false, even though the method being spied upon is called. Look at the following scenario...
I have two classes, written in TypeScript
class Parent() { buyFood() { // buy food } } class Husband extends Parent { makeDinner() { super.buyFood(); // make dinner; } }
In my tests for the class Husband, i'm only concerned of testing the logic for making dinner, since the logic of the buy food of the super class is tested in its own test suite.
Hence, my tests looks like something of the following sort.
let husband:Husband = new Husband(); it('Should make a good dinner', () => { spyOn(husband, 'buyFood'); husband.makeDinner(); expect(husband.buyFood).toHaveBeenCalled(); }
Even though buyFood() is being called, the assertion is failing with an error saying that husband.buyFood() which is the method inherited from the Parent class has never been called.
How should I go about this issue, without having to assert the value changes by the buyFood() method call?
You have to understand the mechanics behind Typescript and the spying.
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.
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.
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 the 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(); }
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