Тестирование приложения backbone.js с помощью jasmine — как проверить привязки модели к представлению?

У меня были некоторые интересные трудности, когда я пытался проверить, правильно ли представления связаны с событиями. В магистрали мы обычно привязываемся к событиям в методе инициализации, используя что-то вроде: something.bind("change", this.render);. В своем тесте я хочу убедиться, что эта привязка настроена, поэтому я сделал следующее:

this.myView = new MyView();
spyOn(this.myView, "render");;
this.legendView.groupData.trigger("change");
expect(this.legendView.render).toHaveBeenCalled();

Но это не сработает. Поскольку привязка происходит в функции инициализации MyView, событие привязывается к функции рендеринга myView В ЭТО ВРЕМЯ. Таким образом, когда вы добавляете своего шпиона, он оборачивает функцию рендеринга и возвращает ее на место в myView.render. Но замыкание, созданное первой привязкой, все еще существует, и мы полностью запутались. Итак, что мы можем с этим поделать? Что я сделал, так это переместил вызов привязки в отдельную функцию, например:

myView = Backbone.View.extend({
initialize: function(){
    _.bindAll(this, "render");
    this.initialize_model_bindings();
},
initialize_model_bindings: function(){
    something.bind("change", this.render);
},
render: function(){ //... }
});

и мой тест выглядит так:

this.myView = new MyView();
spyOn(this.myView, "render");
this.myView.initialize_model_bindings();
this.legendView.groupData.trigger("change");
expect(this.legendView.render).toHaveBeenCalled();

Это работает, но я ищу лучшее решение. Спасибо


person idbentley    schedule 24.06.2011    source источник
comment
Не могли бы вы объяснить этот момент. Но замыкание, созданное первой привязкой, все еще существует, и мы снова полностью запутались? У меня та же проблема, и я пытаюсь понять, что происходит. Спасибо.   -  person trivektor    schedule 18.01.2012
comment
Когда вы вызываете new MyView(), вызывается метод инициализации, и функция рендеринга, существующая в объекте в это время, привязывается к событию изменения. С этого момента невозможно получить доступ к функции рендеринга, которая привязана к событию изменения — она заблокирована в замыкании.   -  person idbentley    schedule 18.01.2012
comment
Спасибо за разъяснения.   -  person trivektor    schedule 19.01.2012


Ответы (6)


Вместо того, чтобы следить за обратным вызовом, вы можете попробовать следить за something.bind. Затем проверьте, что привязка была вызвана с соответствующими аргументами. Это работает для меня до сих пор. Я использую sinon.js вместо встроенных шпионов jasmine. sinon.js немного упрощает проверку аргументов, передаваемых вызову метода в стеке вызовов одного и того же метода (например, набор вызовов для привязки в представлении init). Так что я не проверял эту идею только с жасмином, но считаю, что это возможно.

spyOn(this.legendView.groupData, 'bind');
this.myView = new MyView();
expect(this.legendView.groupData.mostRecentCall.args).toEqual('change', this.myView.render); // example!! only works if testing a single call to bind or the last call in a series (ie mostRecentCall)

И с sinon.js

sinon.spy(this.legendView.groupData, 'bind');
this.myView = new MyView();
expect(this.legendView.groupData.bind.calledWith('change', this.myView.render); // works w/ any number of calls to bind
person Jeremiah    schedule 11.07.2011

Мне удалось добиться этого с помощью исправления прототипа. Прежде чем создавать экземпляр представления, spyOn создайте прототип конструктора.

spyOn(MyView.prototype, 'changeSelected');
var view = new MyView();
view.selectSomething();
expect(view.changeSelected).toHaveBeenCalled();
person mjtamlyn    schedule 23.09.2011
comment
по какой-то причине это сработало для меня только с использованием sinon.spy(...). спасибо за эту подсказку! - person fifigyuri; 22.11.2012

Я решил эту проблему, шпионя за функцией, вызываемой моей функцией рендеринга. Итак, в вашем примере:

myView = Backbone.View.extend({
  initialize: function(){
      _.bindAll(this, "render");
      something.bind("change", this.render);
  },
  someOtherFunction: function(){},  //this function only called from render
  render: function(){ this.someOtherFunction(); /* rest of render function */ }
});

тест выглядит так:

this.myView = new MyView();
spyOn(this.myView, "someOtherFunction");
this.myView.something.trigger("change");
expect(this.myView.someOtherFunction).toHaveBeenCalled();  

затем я написал отдельный тест для всего, что делает someOtherFunction.

person mdan    schedule 20.07.2011
comment
Это кажется неплохим решением, но я обнаружил, что мои функции рендеринга меняются довольно часто. Я чувствую, что я бы сильно обновил свои спецификации, если бы сделал это. - person idbentley; 08.08.2011

Вам следует подумать о том, чтобы взглянуть на Sinon.js. Вы можете заглушить/издеваться над вызовом render() и даже не беспокоиться о 'someOtherFunction()'.

person James Brown    schedule 31.07.2011

Это может быть слишком тесно связано с внутренностями Backbone, но вы можете проверить цепочку обратного вызова вручную:

expect(this.legendView.groupData._callbacks['change']).toContain(this.myView.render)
person Neil Sarkar    schedule 08.08.2011

Я столкнулся с той же проблемой и изменил код Views с:

this.model.on('change', this.render, this);

to:

this.model.on('change', function () {
    this.render();
}, this);

И мои тесты на жасмин сработали, как и ожидалось.

person Peeter    schedule 26.10.2012