web-dev-qa-db-ja.com

継承されたメソッド(TypeScriptを使用)のジャスミンスパイがtoHaveBeenCalled()で期待どおりに機能しない

TypeScriptクラスの呼び出しの継承されたメソッドをスパイするときに、スパイされているメソッドが呼び出されてもtoHaveBeenCalled()メソッドがfalseを返すという問題が発生しています。次のシナリオを見てください...

TypeScriptで書かれた2つのクラスがあります

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

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

スーパークラスのバイフードのロジックは独自のテストスイートでテストされるため、Husbandクラスのテストでは、夕食を作るためのロジックをテストすることだけに関心があります。

したがって、私のテストは次のようなもののように見えます。

let husband:Husband = new Husband();

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

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

BuyFood()が呼び出されているにもかかわらず、アサーションが失敗し、Parentクラスから継承されたメソッドである夫.buyFood()が呼び出されたことがないというエラーが発生します。

この問題は、buyFood()メソッド呼び出しによって値の変更を主張する必要なしにどうすればよいですか?

21
JeanPaul A.

TypeScriptとスパイの背後にあるメカニズムを理解する必要があります。

最初にTypeScriptについて...

class Parent()の余分な括弧は無視しています。

TypeScriptは、カーテンの後ろでプロトタイプの継承を使用します。したがって、プロトタイプは「基本クラス」から新しいクラスに参照されるプロパティをコピーします。これは、__extends()関数のforループが行うことです。

これは、TypeScriptが翻訳されたES5コードです。

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

この TypeScript playground を使用してTypeScriptを翻訳できます。

super式は、「継承された」Husbandのメソッドではなく、親クラスのbuyFood()メソッドを呼び出します。

ラインを見る

__super.prototype.buyFood.call(this);
_

__super_リファレンスに従います。

ジャスミンスパイ...

スパイは、渡されたオブジェクトの名前付き関数を、プロキシとして機能するスパイ関数に置き換えます。そのプロキシは、呼び出しを追跡できるようになり、プログラムされた動作に応じて、元の関数を呼び出すか、偽物を呼び出すか、値を返すか、何もしないかを制御します(デフォルト)。

very簡略化されたspyOn()は次のようになります。

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

    spy.calls = [];

    obj[fn] = spy;
}
_

実際のスパイメソッド は、はるかに複雑です。

あなたのライン

_spyOn(husband, 'buyFood');
_

実際には、Husbandinstanceのメソッドをスパイで置き換えます。ただし、コードは基本クラス(親プロトタイプ)の参照を呼び出すため、置き換えた関数とは異なります。

解決

this参照メソッドを呼び出す必要があります

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

...または親プロトタイプ(super)をスパイ:

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

    expect(Parent.prototype.buyFood).toHaveBeenCalled();
}
_
47

ES6を使用する場合、Parent.prototype 動作しないでしょう。使用する Object.getPrototypeOf代わりに。

これは私のために働いたものです:

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

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