web-dev-qa-db-fra.com

jasmine espionne les méthodes héritées (avec TypeScript) ne fonctionnant pas comme prévu avec toHaveBeenCalled ()

Je rencontre actuellement un problème lors de l'espionnage des méthodes héritées pour les appels dans les classes TypeScript, où la méthode toHaveBeenCalled () renvoie false, même si la méthode espionnée est appelée. Regardez le scénario suivant ...

J'ai deux classes, écrites en TypeScript

class Parent() {
    buyFood() {
        // buy food
    }
}

class Husband extends Parent {
    makeDinner() {
        super.buyFood();
        // make dinner;
    }
}

Dans mes tests pour la classe Husband, je ne fais que tester la logique de préparation du dîner, car la logique de l'achat de nourriture de la super classe est testée dans sa propre suite de tests.

Par conséquent, mes tests ressemblent à quelque chose du genre suivant.

let husband:Husband = new Husband();

it('Should make a good dinner', () => {
    spyOn(husband, 'buyFood');
    husband.makeDinner();

    expect(husband.buyFood).toHaveBeenCalled();
}

Même si buyFood () est appelé, l'assertion échoue avec une erreur indiquant que mari.buyFood () qui est la méthode héritée de la classe Parent n'a jamais été appelée.

Comment dois-je résoudre ce problème sans avoir à affirmer les changements de valeur par l'appel de la méthode buyFood ()?

21
JeanPaul A.

Vous devez comprendre la mécanique derrière TypeScript et l'espionnage.

Tout d'abord sur TypeScript ...

J'ignore les parens supplémentaires dans class Parent().

TypeScript utilise l'héritage prototypique derrière le rideau. Ainsi, un prototype copiera les propriétés référencées de la "classe de base" vers la nouvelle classe. C'est ce que fait la boucle for dans la fonction __extends().

Il s'agit du code ES5 dans lequel votre TypeScript est traduit:

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

Vous pouvez traduire TypeScript en utilisant cette aire de jeux TypeScript .

Votre expression super appelle la méthode buyFood() de la classe parente et non la méthode de la "hérité" Husband.

Voir la ligne

_super.prototype.buyFood.call(this);

et suivez la référence _super.

Maintenant, Jasmine Spies ...

Un espion remplacera la fonction nommée de l'objet passé par une fonction d'espionnage qui agira comme un proxy. Ce proxy peut désormais suivre les appels et, selon le comportement programmé, contrôler s'il faut appeler la fonction d'origine, un faux, renvoyer une valeur ou ne rien faire (par défaut).

Un très simplifié spyOn() pourrait ressembler à ceci:

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

    spy.calls = [];

    obj[fn] = spy;
}

La méthode d'espionnage réelle est cependant beaucoup plus complexe.

Votre ligne

spyOn(husband, 'buyFood');

remplacera en fait la méthode dans l'instance de Husband par un espion. Mais, puisque le code appelle la référence de la classe de base (le prototype parent), ce n'est pas la même fonction que vous venez de remplacer.

Solution

Vous devez soit appeler la méthode référencée this

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

... ou espionner le prototype parent (super):

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

    expect(Parent.prototype.buyFood).toHaveBeenCalled();
}
47
try-catch-finally

Lorsque vous utilisez ES6, le Parent.prototype ne fonctionnera pas. Utilisation Object.getPrototypeOf au lieu.

C'est ce qui a fonctionné pour moi:

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

    expect(Parent.prototype.buyFood).toHaveBeenCalled();
}
0
razi136