Jasmine spies on legacy methods (with typescript) doesn't work as expected with toHaveBeenCalled ()

I currently have a problem spying on inherited methods for calls in typescript classes, where the toHaveBeenCalled () method returns false, even if the method that the spy points to 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 Husband class, I only care about checking the lunch logic, since the superclass purchase logic is tested in its own test suite.

Therefore, my tests look like something like the following.

 let husband:Husband = new Husband(); it('Should make a good dinner', () => { spyOn(husband, 'buyFood'); husband.makeDinner(); expect(husband.buyFood).toHaveBeenCalled(); } 

Although buyFood () is called, the statement fails with an error saying that man.buyFood (), which is a method inherited from the Parent class, was never called.

How can I solve this problem without requiring confirmation of the value by calling the buyFood () method?

+14
source share
2 answers

You need to understand the mechanics behind Typescript and spying.

Typescript first ...

I ignore extra partners in class Parent() .

Typescript uses prototype inheritance behind the curtain. Thus, the prototype will copy the reference properties from the "base class" into the new class. This is what the for loop does in __extends() .

This is ES5 code, your Typescript is translated into:

 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 the Typescript playground .

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

See line

 _super.prototype.buyFood.call(this); 

and follow the instructions of _super .

Now Jasmine spies ...

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

A very simplified spyOn() might 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 complicated.

Your line

 spyOn(husband, 'buyFood'); 

will actually replace the method in the Husband instance with a spy. But since the code calls the base class reference (the parent prototype), this is not the same function that you just replaced.

Decision

You must either call this method

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

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

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

When using ES6 , Parent.prototype will not work. Object.getPrototypeOf this use Object.getPrototypeOf .

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

Source: https://habr.com/ru/post/1263376/


All Articles