【问题标题】:QUnit, Sinon.js & Backbone unit test frustration: sinon spy appears to fail to detect Backbone Model event callbacksQUnit、Sinon.js 和 Backbone 单元测试失败:sinon spy 似乎无法检测到 Backbone 模型事件回调
【发布时间】:2012-06-07 14:02:44
【问题描述】:

在下面的单元测试代码中:

TestModel = Backbone.Model.extend({
    defaults: {
        'selection': null
    },
    initialize: function() {
      this.on('change:selection', this.doSomething);
    },
    doSomething: function() {
        console.log("Something has been done.");
    }
});

module("Test", {
    setup: function() {
        this.testModel = new TestModel();
    }
});

test("intra-model event bindings", function() {
    this.spy(this.testModel, 'doSomething');
    ok(!this.testModel.doSomething.called);
    this.testModel.doSomething();
    ok(this.testModel.doSomething.calledOnce);
    this.testModel.set('selection','something new');
    ok(this.testModel.doSomething.calledTwice); //this test should past, but fails.  Console shows two "Something has been done" logs.
});

第三个 ok 失败,即使该函数是从主干事件绑定中有效调用的,正如控制台演示的那样。

这非常令人沮丧,并动摇了我对 sinon.js 是否适合测试我的主干应用程序的信心。我做错了什么,或者这是 sinon 如何检测是否已调用某事的问题?有解决办法吗?

编辑:这是我的具体示例的解决方案,基于已接受答案的猴子补丁方法。虽然它在测试本身中有几行额外的设置代码,(我不再需要模块功能)它完成了工作。谢谢mu is too short

test("intra-model event bindings", function() {
    var that = this;
    var init = TestModel.prototype.initialize;
    TestModel.prototype.initialize = function() {
        that.spy(this, 'doSomething');
        init.call(this);
    };

    this.testModel = new TestModel();
    . . . // tests pass!
}); 

【问题讨论】:

    标签: javascript backbone.js qunit sinon


    【解决方案1】:

    调用this.spy(this.testModel, 'doSomething')testModel.doSomething 方法替换为new wrapper method

    var spy = sinon.spy(object, "method");

    object.method 创建一个spy,并用spy 替换原来的方法。

    所以this.spy(this.testModel, 'doSomething') 正在有效地做这样的事情:

    var m = this.testModel.doSomething;
    this.testModel.doSomething = function() {
        // Spying stuff goes here...
        return m.apply(this, arguments);
    };
    

    这意味着当你在initialize中绑定事件处理程序时,testModel.doSomething是一个不同的函数:

    this.bind('change:selection', this.doSomething);
    

    比你附上你的间谍后的情况。 Backbone 事件调度程序将调用原始的doSomething 方法,但该方法没有Sinon 检测。当您手动调用doSomething 时,您调用的是spy 添加的新函数,并且该函数确实具有Sinon 检测。

    如果您想使用 Sinon 测试您的 Backbone 事件,那么您必须在绑定任何事件处理程序之前安排将 Sinon spy 调用应用于模型,这可能意味着挂钩到 ​​initialize

    也许您可以对模型的 initialize 进行猴子修补,以在绑定任何事件处理程序之前添加必要的 spy 调用:

    var init = Model.prototype.initialize;
    Model.prototype.initialize = function() {
        // Set up the Spy stuff...
        init.apply(this, arguments);
    };
    

    演示:http://jsfiddle.net/ambiguous/C4fnX/1/

    您也可以尝试使用以下方式子类化您的模型:

    var Model = Backbone.Model.extend({});
    var TestModel = Model.extend({
        initialize: function() {
            // Set up the Spy stuff...
            Model.prototype.initialize.apply(this, arguments);
        }
    });
    

    然后使用TestModel 代替模型,这将为您提供TestModelModel 的插桩版本,而无需在您的正常生产就绪Model 中包含一堆特定于测试的代码。缺点是使用Model 的任何其他东西都需要被子类化/修补/...才能使用TestModel

    演示:http://jsfiddle.net/ambiguous/yH3FE/1/

    您可以通过以下方式解决TestModel 问题:

    var OriginalModel = Model;
    Model = Model.extend({
        initialize: function() {
            // Set up the Spy stuff...
            OriginalModel.prototype.initialize.apply(this, arguments);
        }
    });
    

    但您必须正确订购以确保每个人都使用新的Model 而不是旧的。

    演示:http://jsfiddle.net/ambiguous/u3vgF/1/

    【讨论】:

    • 有道理。很好的答案,谢谢!此时,让单元测试在模型本身上运行就足够了。如果我最终做一些有趣的事情来测试 Views 的回调等,我会回帖,我很高兴我很早就发现了这一点。
    • 刚刚找到的相关问题:stackoverflow.com/questions/9113186/…
    • @Ben:我认为猴子修补initialize 方法可能是你最好的选择,这将是相当低的影响和很好的隔离。我添加了一些演示来帮助说明这些想法。
    • 是的,我根据您的示例更新了我的测试用例,以我能想到的最简单的方式,并且成功了。请参阅上面对我的问题的更新。
    • @Ben:应该可以。如果您有一个使用模型的集合,并且您想监视两者,那么猴子修补 initialize 方法可能是最简单的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-25
    • 2015-02-15
    • 1970-01-01
    • 2014-02-04
    • 2017-02-12
    • 1970-01-01
    • 2014-05-20
    相关资源
    最近更新 更多