2015-08-06 2 views
1

Я изучаю Магистраль и хочу «высмеять» результаты вызова .fetch() в рамках модели. Я не хочу использовать тестовую библиотеку или на самом деле удалять внешнюю службу.mock Ответ JSON в Backbone Fetch?

В основном у меня есть настройка в моей модели, где, если this.options.mock === true, то просто используйте внутренний объект JSON в качестве «результата» извлечения. Кроме того, на самом деле он обращается к API с помощью реального запроса AJAX.

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

Есть ли способ подделать ответ Fetch в магистрали без привлечения библиотеки тестирования, такой как Sinon?

вот полная модель (по крайней мере, соответствующие ее части). В принципе, модель извлекает данные и форматирует их для шаблона. а затем вид, который владеет моделью, отображает его.

'use strict'; 
(function (app, $, Backbone) { 

    app.Models.contentModel = Backbone.Model.extend({ 

     /** 
     * Initializes model. Fetches data from API. 
     * @param {Object} options Configuration settings. 
     */ 
     initialize: function (options) { 
      var that = this; 
      that.set({ 
       'template': options.template, 
       'mock': options.mock || false 
      }); 

      $.when(this.retrieveData()).then(function (data) { 
       that.formatDataForTemplate(data); 
      }, function() { 
       console.error('failed!'); 
      }); 
     }, 

     retrieveData: function() { 

      var that = this, deferred = $.Deferred(); 

      if (typeof fbs_settings !== 'undefined' && fbs_settings.preview === 'true') { 
       deferred.resolve(fbs_settings.data); 
      } 
      else if (that.get('mock')) { 
       console.info('in mock block'); 

       var mock = { 
        'title': 'Test Title', 
        'description': 'test description', 
        'position': 1, 
        'byline': 'Author' 
       }; 

       deferred.resolve(mock); 
      } 
      else { 
       // hit API like normal. 
       console.info('in ajax block'); 
       that.fetch({ 
        success: function (collection, response) { 
         deferred.resolve(response.promotedContent.contentPositions[0]); 
        }, 
        error: function(collection, response) { 
         console.error('error: fetch failed for contentModel.'); 
         deferred.resolve(); 
        } 
       }); 
      } 
      return deferred.promise(); 
     }, 

     /** 
     * Formats data on a per-template basis. 
     * @return {[type]} [description] 
     */ 
     formatDataForTemplate: function (data) { 
      if (this.get('template') === 'welcomead_default') { 
       this.set({ 
        'title': data.title, 
        'description': data.description, 
        'byline': data.author 
       }); 

      } 
      // trigger the data formatted event for the view to render. 
      this.trigger('dataFormatted'); 
     } 
    }); 
})(window.app, window.jQuery, window.Backbone); 

Соответствующий бит с точки зрения (ContentView):

this.model = new app.Models.contentModel({template: this.templateName}); 
this.listenTo(this.model, 'dataFormatted', this.render); 

ли данные быть установлены так быстро, что слушатель не был установлен еще?

+0

Не могли бы вы включить немного больше кода, окружающего ваш блок if-else? Где в модели это и как она называется? Запомните [mcve] (https://stackoverflow.com/help/mcve). Из кода, который вы опубликовали, для кого-то еще трудно определить, что случилось. – ivarni

+0

'fetch()' запускает ряд зависимых событий, которые вы не можете получить таким способом. Вы должны переопределить [Backbone.sync] (http://backbonejs.org/#Sync) – hindmost

+0

@ivarni Я добавил свою полную модель к исходному сообщению. – Prefix

ответ

1

Проблема здесь не с фактической реализации retrieveData, но с тем, как его называют. Когда вы разрешаете отложенные перед возвратом, вы в основном делаете это мгновенно. Это приводит к выходу formatDataForTemplate, пока ваша модель все еще инициализируется.

Так что, когда вы делаете

this.model = new app.Models.contentModel({template: this.templateName}); 
this.listenTo(this.model, 'dataFormatted', this.render); 

dataFormatted событие заканчивается время срабатывает перед слушателем прописал.

Одним из решений является использование тайм-аут, который должен работать только с

setTimeout(function() { 
    deferred.resolve(mock); 
}); 

как задержит решимость недо следующий раунд цикла событий, когда слушатель находится на месте.

Другим решением, не связанным с setTimeout, было бы не звонить retrieveData во время инициализации модели, а позволять виду делать это после того, как он подключил его слушателей.

this.model = new app.Models.contentModel({template: this.templateName}); 
this.listenTo(this.model, 'dataFormatted', this.render); 
this.model.retrieveData(); 

Я бы предпочел последнее, но если это всего лишь смехотворные данные для работы в автономном режиме, на мой взгляд это не имеет никакого значения.

Unrelated к тому, что стоит отметить, что фактическая signature для Initialize на модели new Model([attributes], [options]) поэтому ваш инициализации должен вероятно выглядеть следующим образом

initialize: function (attributes, options) { 
    var that = this; 
    that.set({ 
     'template': options.template, 
     'mock': options.mock || false 
    }); 

Просто ради удобства чтения. Это снова означает, что, поскольку вы передаете только один объект, вам не нужно вообще звонить set.

+0

Отличные подсказки. Я изменил свой код, чтобы избежать setTimeout, нарушив вызов retrieveData из инициализации. Также не знал о возможности «Model ([attributes])», поэтому я тоже изменился. Спасибо! – Prefix

+0

@Prefix Счастливые помочь. Как правило, вы не хотите делать ничего, что потенциально может коснуться DOM при инициализации функций. Это относится и к представлениям, моделям и коллекциям. – ivarni

+0

Я вижу! Должны ли все манипуляции с dom быть включены в метод рендеринга? Какие вещи я должен делать внутри 'initialize()' в, скажем, в представлении? Еще раз спасибо. – Prefix

2

Вы можете переопределить функцию выборки следующим образом.

var MockedModel = Backbone.Model.extend({ 
 
    initialize: function(attr, options) { 
 
    if (options.mock) { 
 
     this.fetch = this.fakeFetch; 
 
    } 
 
    }, 
 
    url: 'http://someUrlThatWIllNeverBeCalled.com', 
 
    fakeFetch: function(options) { 
 
    var self = this 
 
    this.set({ 
 
     'title': 'Test Title', 
 
     'description': 'test description', 
 
     'position': 1, 
 
     'byline': 'Author' 
 
    }); 
 

 
    if (typeof options.success === 'function') { 
 
     options.success(self, {}, {}) 
 
    } 
 
    } 
 
}); 
 
var mockedModel = new MockedModel(null, { 
 
    mock: true 
 
}) 
 
mockedModel.fetch({ 
 
    success: function(model, xhr) { 
 
    alert(model.get('title')); 
 
    } 
 
});
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script> 
 
    <script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js"></script> 
 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Смежные вопросы