Состояние гонки в Backbone при выборке данных в модульных тестах

Мы используем Backbone (плохо) и Handlebars, Mocha и SinonJS для тестирования. Я постоянно сталкиваюсь с проблемой при попытке модульного тестирования кода других разработчиков. Кажется, есть проблема, когда модель или коллекция извлекает данные. Асинхронный вызов не всегда завершается при входе в мой модульный тест, поэтому я часто вижу эти ложные срабатывания на сервере сборки.

Разработчики не используют LayoutManager или любой другой инструмент для управления жизненным циклом представления. Могу ли я добавить в представление функцию afterRender, чтобы обеспечить загрузку всех данных? Если да, то как будет выглядеть эта функция? Буду ли я использовать обещание jQuery?

Я попытался издеваться над запросами в методе проверки beforeEach и вызвать функцию done, но проблема, похоже, не устранена. Может ли кто-нибудь указать мне на что-то, что может помочь мне убедиться, что представление отрисовано и выборка завершена перед входом в тест? Любая помощь будет здорово. Спасибо!


person Mateo    schedule 20.11.2014    source источник


Ответы (1)


У меня была такая же проблема, и вот что я сделал.

Помощник по обратным вызовам

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

Основная коллекция

В этой коллекции у нас есть 2 важных метода: onReset() и collectionReset(). Метод onReset() должен быть тем, как представления "прослушивают" событие reset коллекции, поскольку он использует промисы.

Метод collectionReset() выполняется только после запуска события reset коллекции, в этот момент мы считаем коллекцию загруженной и разрешаем все добавленные обратные вызовы.

Это учитывает условия гонки, потому что обратные вызовы были добавлены с использованием метода 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;
});
person istos    schedule 20.11.2014
comment
Очень красивое объяснение! Очень тщательно! Я обязательно воспользуюсь этим! - person Mateo; 22.11.2014