【问题标题】:jasmine spies on inherited methods (with typescript) not working as expected with toHaveBeenCalled()茉莉花窥探继承的方法(使用打字稿)无法按预期使用 toHaveBeenCalled()
【发布时间】:2017-06-11 16:32:36
【问题描述】:

我目前在监视 typescript 类中调用的继承方法时遇到问题,其中 toHaveBeenCalled() 方法返回 false,即使调用了被监视的方法。看下面的场景……

我有两个类,用 TypeScript 编写的

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() 方法调用断言值更改?

【问题讨论】:

  • 测试框架是否支持它的任何地方都有记录? JavaScript 中的继承充其量是善变的。
  • 似乎没有任何对继承方法的引用...来自文档“间谍可以存根任何函数并跟踪对其的调用和所有参数。”
  • 那么也许它没有走原型链。

标签: javascript inheritance typescript jasmine


【解决方案1】:

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

【讨论】:

  • 对我有用,但奇怪的是 Object.getPrototypeOf() 必须调用两次
  • 我认为这是因为第一次调用会得到类的原型,第二次调用会得到原型的原型
  • P.S.此解决方案有效,但您的示例在“丈夫”之后缺少尾括号。
【解决方案2】:
const spy = spyOn(husband, 'buyFood');
husband.makeDinner();
expect(spy).toHaveBeenCalled();

【讨论】:

  • 最好添加一句话来解释什么是错误的以及建议的代码正在修复什么
【解决方案3】:

您必须了解 Typescript 和间谍活动背后的机制。

首先在 Typescript ...

我忽略了class Parent() 中的额外括号。

Typescript 在幕后使用原型继承。因此,原型会将引用的属性从“基类”复制到新类。这就是for 循环在__extends() 函数中的作用。

这是你的 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 翻译打字稿。

您的super 表达式调用父类的buyFood() 方法,而不是“继承的”Husband 的方法。

看线

_super.prototype.buyFood.call(this);

并按照_super 参考。

现在是茉莉花间谍 ...

间谍将用充当代理的间谍函数替换传递对象的命名函数。该代理现在可以跟踪调用,并根据编程行为控制是调用原始函数、伪造函数、返回值还是什么都不做(默认)。

非常简化的spyOn() 可能如下所示:

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

    spy.calls = [];

    obj[fn] = spy;
}

actual spy method 不过要复杂得多。

你的线路

spyOn(husband, 'buyFood');

实际上会将Husband实例 中的方法替换为间谍。但是,由于代码调用了基类(父原型)的引用,它与您刚刚替换的函数不同。

解决方案

您应该调用 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();
}

【讨论】:

  • 超级!!非常感谢详细的解释和解决方案!真的真的很感谢! :D
  • 这非常有帮助!在我的例子中,我在一个子类中有一个方法,它隐藏了一个名为方法的超类,所以spyOn(Parent.prototype, 'methodName'); 是完美的!
  • 解决方案完美运行!最重要的是,感谢您巧妙地解释它的工作原理:)
  • 哇。喜欢这个解决方案!
  • @try-catch-finally 如果 buyFood() 被“保护”了怎么办?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-19
  • 2020-09-09
  • 1970-01-01
  • 2019-01-24
  • 1970-01-01
  • 2023-03-16
相关资源
最近更新 更多