Подтвердить что ты не робот

Backbone.js click event spy не вызывается с использованием jasmine.js и sinon.js

Я пытаюсь проверить нажатие кнопки с помощью backbone.js, jasmine.js и sinon.js. Но следующий тестовый пример терпит неудачу. Я использую шпион, чтобы отслеживать, будет ли он вызван или нет. Не могли бы вы помочь мне с этим?

Спасибо.

Новый шаблон задачи

<script id='new_task_template' type='text/template'>
  <input type='text' id='new_task_name' name='new_task_name'></input>
  <button type='button' id='add_new_task' name='add_new_task'>Add Task</button>
</script>

NewTaskView

T.views.NewTaskView = Backbone.View.extend({
  tagName: 'section',
  id: 'new_task_section',
  template : _.template ( $("#new_task_template").html() ),
  initialize: function(){
    _.bindAll( this, 'render', 'addTask');
  },
  events:{
    "click #add_new_task" : "addTask"
  },
  render: function(){
    $(this.el).html( this.template() );
    return this;
  },
  addTask: function(event){
    console.log("addTask");
  }
});

Тест на жасмин

describe("NewTaskView", function(){
  beforeEach( function(){    
    this.view = new T.views.NewTaskView();
    this.view.render();
  });

  it("should #add_new_task is clicked, it should trigger the addTask method", function(){
    var clickSpy = sinon.spy( this.view, 'addTask');
    $("#add_new_task").click();
    expect( clickSpy ).toHaveBeenCalled();
  });
});

Выход жасмина

NewTaskView
  runEvents
    runshould #add_new_task is clicked, it should trigger the addTask method
      Expected Function to have been called.
4b9b3361

Ответ 1

Проблема заключается в том, что вы добавляете своего шпиона после того, как позвоночник уже привязал событие click непосредственно к функции addTask (он делает это во время построения View). Поэтому ваш шпион не будет вызван.

Попробуйте подключить шпиона к прототипу представления перед его созданием. Вот так:

this.addTaskSpy = sinon.spy(T.views.NewViewTask.prototype, 'addTaskSpy');
this.view = new T.views.NewTaskView();

а затем не забудьте удалить его:

T.views.NewViewTask.prototype.addTaskSpy.restore()

Ответ 2

events:{
    "click" : "addTask"
},

означает, что вы привязываете событие click к this.el - корневому элементу представления, которое в вашем случае имеет идентификатор new_task_section. Вам нужно привязать его к #add_new_task, который является вашей кнопкой "добавить задачу", которую я предполагаю, - это должно исправить это!

events:{
    "click #add_new_task" : "addTask"
},

Update:

$( "# add_new_task" ) не найдет элемент, поскольку представление не добавлено в дерево DOM документа. используйте this.view.$('#add_new_task'), и он должен работать, поскольку он будет искать элемент в отдельном фрагменте, хранящемся в представлении.

Ответ 3

Есть некоторые проблемы с вашим подходом. Сначала вы шпионите за своим классом, который хотите испытать, потому что это не способ работать unit test, потому что вы проверяете внутреннюю логику своего класса, а не его поведение. Во-вторых, и это причина, по которой ваш тест терпит неудачу, вы не привязали свои взгляды в DOM. Таким образом, либо вы прикрепляете ваш el к DOM, либо запускаете событие click непосредственно в el: $('#add_new_task', this.view.el).click().

Btw. магистральный способ создания элементов и привязки событий затрудняет запись хороших модульных тестов, потому что вы вынуждены работать с DOM и jquery. Лучшим способом написания тестового кода было бы всегда передавать все зависимости в конструктор и не создавать новые экземпляры в коде, потому что его трудно проверить эти объекты. Таким образом, в вашем случае было бы намного проще вставить объект el в конструкторе как объект jquery и в событиях вручную. Таким образом, вы можете проверить свой класс без каких-либо зависимостей с DOM или jquery.

Итак, в вашем случае конструктор будет выглядеть так:

initialize: function(){
   this.el.click(_.bind( this, 'addTask'));
}

И ваш тест:

var el = {click: function(){}};
spyOn( el, 'click');
new T.views.NewTaskView({el: el});
expect(el.click).toHaveBeenCalled(); //test the click event was bind
//call the function that was bind to the click event, 
//which is the same as trigger the event on a real DOM object
el.click.mostRecentCall.args[0]() 

В конце концов, вам нужно решить, какой подход будет соответствовать вашим потребностям. Leaner-код с помощниками магистралей или лучше тестируемый код с меньшими зависимостями от jQuery, DOM и магистрали.