在单元测试中获取数据时Backbone中的竞争条件

时间:2014-11-20 00:42:16

标签: javascript jquery unit-testing backbone.js race-condition

我们使用Backbone(很差)和Handlebars,Mocha和SinonJS进行测试。在尝试对其他开发人员代码进行单元测试时,我不断遇到问题。似乎模型或集合获取数据时存在问题。进入我的单元测试时,异步调用并不总是完成,所以我经常在构建服务器上看到这些误报。

开发人员没有使用 LayoutManager 或任何其他工具来管理视图生命周期。我可以在视图中添加afterRender函数以确保加载所有数据吗?如果是这样,那个功能会是什么样子?我会利用jQuery的承诺吗?

我曾尝试在beforeEach测试方法中模拟请求并调用done函数,但问题似乎仍然存在。有人能指出我可以帮助我确保在进入测试之前呈现视图并完成获取的东西吗?任何帮助都会很棒。谢谢!

1 个答案:

答案 0 :(得分:0)

我有同样的问题,这就是我所做的。

回调助手

// The Callbacks module.
//
// A simple way of managing a collection of callbacks
// and executing them at a later point in time, using jQuery's
// `Deferred` object.
// 
// Source: https://github.com/derickbailey/backbone.marionette/blob/master/lib/backbone.marionette.js#L1291
define(['jquery'], function($) {

    function Callbacks() {
        this.deferred = $.Deferred();
        this.promise = this.deferred.promise();
    }

    Callbacks.prototype = {
        constructor: Callbacks,

        // Add a callback to be executed. Callbacks added here are
        // guaranteed to execute, even if they are added after the
        // `run` method is called.
        add: function(callback, contextOverride) {
            this.promise.done(function(context, options) {
                if (contextOverride) {
                    context = contextOverride;
                }

                callback.call(context, options);
            });
        },

        // Run all registered callbacks with the context specified.
        // Additional callbacks can be added after this has been run
        // and they will still be executed.
        run: function(options, context) {
            this.deferred.resolve(context, options);
        }
    };


    return Callbacks;
});

核心馆藏

在此集合中,我们有两个重要方法:onReset()collectionReset()onReset()方法应该是视图" listen"对于集合重置事件,因为它使用了Promises。

collectionReset()方法仅在收集了重置事件时才会执行,此时我们会考虑加载该集合并且我们解决所有添加的回调。

这会处理竞争条件,因为回调是使用onReset()方法添加的,即使在添加回调之前触发了事件也会执行。

当您创建新集合并使其从此集合继承时,您必须确保在获取时添加reset选项:(new ExampleCollection()).fetch({ reset: true })

// The Core Collection - other collections inherit from this one.
// Source: http://lostechies.com/derickbailey/2012/02/03/get-a-model-from-a-backbone-collection-without-knowing-if-the-collection-is-loaded
define([
    'jquery',
    'backbone',
    'app/callbacks'
], function($, Backbone, Callbacks) {

    var CoreCollection = Backbone.Collection.extend({
        constructor: function() {
            Backbone.Collection.prototype.constructor.apply(this, [].slice.call(arguments));

            this.onResetCallbacks = new Callbacks();
            this.on('reset', this.collectionReset, this);
        },

        // The `onReset` method should be called by views to render collection data
        // when the particular collection has been loaded from the server.
        onReset: function(callback, contextOverride) {
            this.onResetCallbacks.add(callback, contextOverride);

            if (this.loaded) {
                this.onResetCallbacks.run(this);
            }
        },

        // Since this method is called only on the collection's `reset`
        // event, we assume that the collection has been fully loaded.
        collectionReset: function() {
            if (!this.loaded) {
                this.loaded = true;
            }

            // Execute all the `onResetCallbacks` callbacks.
            this.onResetCallbacks.run(this);
        }
    });

    return CoreCollection;
});

示例视图

在示例视图中,您将执行以下操作:

define([
    'jquery',
    'backbone',
    'handlebars',
],
function($, Backbone, Handlebars) {
    var ExampleView = Backbone.View.extend({
        id: 'example-view',
        template: Handlebars.templates.example,

        events: {
        },

        initialize: function() {
            // If it renders a collection from the server.
            this.collection = __collection_name__;
            this.collection.onReset(function() {
                // It's guaranteed to execute.
                // No race condition anymore!
                // ...
            });
        }
    });


    return ExampleView;
});