Dave K
Dave K

Reputation: 71

Test Driving Backbone view events

I am trying to test drive a view event using Jasmine and the problem is probably best explained via code.

The view looks like:

App.testView = Backbone.View.extend({
  events: { 'click .overlay': 'myEvent' },
  myEvent: function(e) {
    console.log('hello world')
  }

The test looks something like:

describe('myEvent', function() {
  it('should do something', function() {
    var view = new App.testView();
    view.myEvent();
    // assertion will follow
  });
});

The problem is that the view.myEvent method is never called (nothing logs to the console). I was trying to avoid triggering from the DOM. Has anyone had similar problems?

Upvotes: 0

Views: 1439

Answers (1)

jevakallio
jevakallio

Reputation: 35920

(Like I commented in the question, your code looks fine and should work. Your problem is not in the code you posted. If you can expand your code samples and give more info, we can take another look at it. What follows is more general advice on testing Backbone views.)

Calling the event handler function like you do is a legitimate testing strategy, but it has a couple of shortcomings.

  1. It doesn't test that the events are wired up correctly. What you're testing is that the callback does what it's supposed to, but it doesn't test that the action is actually triggered when your user interacts with the page.
  2. If your event handler needs to reference the event argument or the test will not work.

I prefer to test my views all the way from the event:

var view = new View().render();
view.$('.overlay').click();

expect(...).toEqual(...);

Like you said, it's generally not advisable to manipulate DOM in your tests, so this way of testing views requires that view.render does not attach anything to the DOM.

The best way to achieve this is leave the DOM manipulation to the code that's responsible for initializing the view. If you don't set an el property to the view (either in the View.extend definition or in the view constructor), Backbone will create a new, detached DOM node as view.el. This element works just like an attached node - you can manipulate its contents and trigger events on it.

So instead of...

View.extend({el: '#container'});

...or...

new View({el:'#container'});

...you should initialize your views as follows:

var view = new View();
$("#container").html(view.render().el);

Defining your views like this has multiple benefits:

  1. Enables testing views fully without attaching them to DOM.
  2. The views become reusable, you can create multiple instances and render them to different elements.
  3. If your render method does some complicated DOM manipulation, it's faster to perform it on an detached node.
  4. From a responsibility point of view you could argue that a view shouldn't know where it's placed, in the same way a model should not know what collection it should be added to. This enforces better design of view composition.

IMHO, this view rendering pattern is a general best practice, not just a testing-related special case.

Upvotes: 5

Related Questions