backbone.js click event spy is not getting called using jasmine.js and sinon.js

I am trying to test a button click using backbone.js, jasmine.js and sinon.js. But the following test case fails. I am using a spy to track whether it is getting called or not. Can you please help me with this?

Thanks.

New Task Template

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

Jasmine Test Case

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

Jasmine Output

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

Answers:

Answer

The problem is you add your spy after backbone has already bound the click event directly to the addTask function (it does that during construction of the View). Therefore your spy will not get called.

Try attaching the spy to a prototype of the View before you construct it. Like this:

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

and then remember to remove it:

T.views.NewViewTask.prototype.addTaskSpy.restore()
Answer
events:{
    "click" : "addTask"
},

means that you are binding the click event to this.el - root element of the view which in your case have an ID new_task_section. You need to bind it to #add_new_task which is your 'add task' button I assume - this should fix it!

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

Update:

$("#add_new_task") won't find the element as the view isn't added to the document DOM tree. use this.view.$('#add_new_task') and it should work as it will search for the element in the detached fragment stored in the view.

Answer

There are some problems with your approach. First you spy on your class you wanna test, with is not the way a unit test should work cause you test the inner logic of your class and not its behavior. Second, and thats the reason why your test fail, you dont have attached your views el into the DOM. So either you attache your el to the DOM or fire the click event directly to the el: $('#add_new_task', this.view.el).click().

Btw. backbones way to create element and bind events makes it hard to write good unit tests cause you be forced to work with the DOM and jquery. A better way to write testable code would be to always pass all dependencies in to the constructor and dont create new instances in your code cause its hard to test these objects. So in your case it would be much easier to inject the el object in the constructor as a jquery object and at the events manually. Doing it this way you can test you class without any dependencies to the DOM or jquery.

So in your case the constructor would look like this:

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

And your test:

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]() 

After all you have to decide what approach will fit your needs. Leaner code with backbones helpers or better testable code with less dependencies to jquery, DOM and backbone.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.