Thursday, November 17, 2011

Backbone.js Dependency Injection Makes Testing Easy

‹prev | My Chain | next›

At one point I had my entire Backbone.js application (plus some required, supporting HTML) all in a single web page. It was quite huge.

Having worked with the code quite a bit recently, the entire contents of the <body> tag is now:
<h1>
  Funky Calendar
  <span class="year-and-month"></span>
</h1>

<div id="calendar"></div>

<script>
$(function() {
  window.calendar = new Cal($('#calendar'));
});
</script>
The actual Backbone code (Cal) is now defined entirely in a separate calendar.js file. That source file is loaded via <script> tag along with backbone.js and other Javascript dependencies.

What I hope this means in practice is that I can do away with some of the testing complexity that I have in place. Until now, I have needed to generate a testing fixture from the web page (since it defined so much of what was critical to running the app). For that, I had a very simple expresso test that ran my express.js server and dumped the homepage to the fixture directory. Then, in my jasmine test, I loaded that fixture into the DOM for testing with jasmine-jquery's loadFixtures().

Now, I need only a DOM element to which I can attach my application:
new Cal($('#calendar'));
So, I replace my fixture loading:
  beforeEach(function() {
    // ...
    loadFixtures('homepage.html');
    // ...
  });
Instead, I create a DOM element and inject it into my Backbone application:
  beforeEach(function() {
    // ...
    $('body').append('<div id="calendar"/>');
    window.calendar = new Cal($('#calendar'));
    // ...
  });
I do have to remember to remove that element after each test:
  afterEach(function() {
    $('#calendar').remove();
    $('#calendar-navigation').remove();
  });
Amazingly... it just works:

Without my fixture data, I am tempted to remove jasmine-jquery. I think better of that idea because I really like the toHaveText() matchers:
  describe("appointments", function() {
    it("populates the calendar with appointments", function() {
      expect($('#' + fifteenth)).toHaveText(/Get Funky/);
    });
  });
That was unexpectedly easy. It kinda make me wonder why I did not avoid fixtures in the first place. Ah well, lesson learned.


Day #208

2 comments:

  1. why even give the calendar an element? Backbone will make one not on the dom if you don't provide one.

    If you need it for tests,

    var cal = new Cal();
    var el = cal.el

    -Nick

    ReplyDelete
  2. Fair point. The DI was more important under the covers than it is for the application.

    Still, from an API standpoint, I rather prefer:

    var cal = new Cal($('#calendar'));

    To:

    var cal = new Cal();
    $('#calendar').append(cal.el);

    The former feels like clearer intent to me.

    ReplyDelete