Friday, November 18, 2011

Testing Backbone.js with Standalone Jasmine

‹prev | My Chain | next›

Up tonight, I think I would like to play around a bit with "standalone" jasmine specs for my Backbone.js application. For the past week or so, I have refactored my tests and application to the point that I do not need fixture pages. Tonight I wonder if I can do away with the testing server from jasmine ruby gem.

I grab the ZIP file from http://pivotal.github.com/jasmine/download.html and extract it into my spec directory:
./lib/jasmine-1.1.0/MIT.LICENSE
./lib/jasmine-1.1.0/jasmine-html.js
./lib/jasmine-1.1.0/jasmine_favicon.png
./lib/jasmine-1.1.0/jasmine.css
./lib/jasmine-1.1.0/jasmine.js
Also in my spec directory, I have my spec file (contains the actual tests), some helper testing libraries that I have been using and a web page to load everything into:
./HomepageSpec.js
./helpers/jasmine-jquery-1.3.1.js
./helpers/sinon-1.1.1.js
./SpecRunner.html
The SpecRunner.html is adopted from the one in jasmine-core. The only changes that I make to it are linking in the library files (backbone, jQuery, etc.), test helper libraries, the actual test file and my Backbone application:
  <link rel="stylesheet" type="text/css" href="lib/jasmine-1.1.0/jasmine.css">
  <script type="text/javascript" src="lib/jasmine-1.1.0/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-1.1.0/jasmine-html.js"></script>

  <!-- Library files -->
  <script type="text/javascript" src="../public/javascripts/jquery.min.js"></script>
  <script type="text/javascript" src="../public/javascripts/jquery-ui.min.js"></script>
  <script type="text/javascript" src="../public/javascripts/underscore.js"></script>
  <script type="text/javascript" src="../public/javascripts/backbone.js"></script>

  <!-- Test Helpers -->
  <script type="text/javascript" src="helpers/jasmine-jquery-1.3.1.js"></script>
  <script type="text/javascript" src="helpers/sinon-1.1.1.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="HomepageSpec.js"></script>

  <!-- include source files here... -->
  <script type="text/javascript" src="../public/javascripts/calendar.js"></script>
When I load up file:///home/cstrom/repos/calendar/spec/SpecRunner.html, I find that the browser gets redirected to a non-existent hash-tag on the file system. This is due to my default Backbone route:
  var Routes = Backbone.Router.extend({
    routes: {
      "": "setDefault",
      "month/:date": "setMonth"
    },

    setDefault: function() {
      console.log("[setDefault]");
      var month = Helpers.to_iso8601(new Date).substr(0,7);
      window.location = '/#month/' + month;
    },
    // ...
  });
For the time being, I comment out the Router initialization:
  // new Routes({application: application});
  // try {
  //   Backbone.history.start();
  // } catch (x) {
  //   console.log(x);
  // }
If I can get standalone working, I will revisit the routing.

Unfortunately, almost none of the specs are working:


It takes me a while to track down my issue, but it was merely how I was setting the application date in relation to a sinon.js fake response:
beforeEach(function() {
    // stub XHR requests with sinon.js
    server = sinon.fakeServer.create();

    $('body').append('<div id="calendar"/>');
    window.calendar = new Cal($('#calendar'));

    // populate appointments for this month
    server.respondWith('GET', /\/appointments/,
      [200, { "Content-Type": "application/json" }, JSON.stringify(doc_list)]);
    server.respond();

    window.calendar.application.setDate("2011-11");
  });
Here, I am telling the server to respond to a GET request for the list of appointments with a JSON representation of the document list. The problem is that I am instructing the response to be sent before I trigger a fetch() with my call to the application's setDate() method.

The solution is easy enough. I simply move the setDate() call to immediately before the fake response is sent:
beforeEach(function() {
    window.calendar.application.setDate("2011-11");

    // populate appointments for this month
    server.respondWith('GET', /\/appointments/,
      [200, { "Content-Type": "application/json" }, JSON.stringify(doc_list)]);
    server.respond();
  });
With that, I have all of my specs passing again:

That is pretty darn nice. Thanks in part to sinon.js, I have a very solid test suite over my application. It is not quite end-to-end, but as long as the server response does not change on me, I have the next best thing. Best of all, my testing setup has been reduced to a single file that can be loaded directly by the browser. To be sure, the ruby gem server has advantages, but lightweight testing like this is just nifty.

The only real drawback was the difficulty in tracking down that lack-of-response from sinon.js. Unlike the server based sinon.js setup, this file based setup did not return a default 404 error. The response just seemed to be swallowed silently.

Then again, I may just be too tired to thing straight. I think I will call it a night here and will pick back up with this tomorrow.

Day #209

2 comments:

  1. where are you adding the reference to your code? I see you added the spec in HTML. Don't you need to add references to the actual code under test?

    e.g. if I have a BB model: MyModel.js
    and I wrote specs for it MyModel-spec.js

    I'll need to refer both of them in SpecRunner.html? That seems a lot of work adding reference to every single js file.

    ReplyDelete
  2. @nEEbz Yah, the section labelled "include source files here..." is where the code under test is pulled in. And yah, it could get tedious. I was more trying this out to see if it was possible rather than exploring a best practice :)

    I would say that, when you are using something like require.js and there is only a single file to pull in, then something like this approach does make more sense.

    ReplyDelete