Thursday, June 7, 2012

Avoiding XMLHTTPRequest problem using jQuery on Node.js

We are using Mocha to test a Backbone application on node.

We decided to change from Jasmine to Mocha because we prefer how it handles asynchronous calls using done callback. The other thing we like from Mocha is the possibility of running tests on node. Since a big part of our code did not depend on the DOM, this freed us from having to configure a html file just to load ours tests.

We have a development server running flask at http://localhost:5000 that exposes a REST API for our backbone models. When we were running our tests on a browser, we needed to had a static html file served by flask which had to be configured in order to load all the libraries required to run our tests.
When we started running the tests on node, that html file wasn't needed anymore, but we had to change the backbone collections url from relative to absolute and use require to load the libraries.
This is how our tests looked like:
var assert = require('assert');
var should = require('should');
var jQuery = require("jquery");
var Backbone = require('backbone');

Backbone.setDomLibrary(jQuery);

var BMModel = Backbone.Model.extend({
    idAttribute: "_id"
});

var BMCollection = Backbone.Collection.extend({
    model: BMModel,
    url : "nn"
});

describe("banana", function(){

    describe("read all", function(){
        var bmcollection;
        var expected;

        beforeEach(function(done){
            bmcollection = new BMCollection();
            bmcollection.url = 'http://localhost:5000/collection_tests/';
            bmcollection.fetch({success: function(){
                done();
            }});
        });

        it("gets contents from banana", function(){
            bmcollection.models.should.have.lengthOf(2);
        });
But we were getting an unexpected error with the following message:
No Transport.
This was happening because jQuery, by default, does domain detection. We turned this off by setting:
jQuery.support.cors = true;
Since the tests weren't running in a browser, we thought that this was enough to get rid of of the same origin trap.
We were wrong, because we started to get this other error message:
Object # has no method 'open'
After some googling we found someone seeking help at stackoverflow to solve the same problem.
They recommended using the node-XMLHttpRequest node module which is
a wrapper for the built-in http client to emulate the browser XMLHttpRequest object.
This can be used with JS designed for browsers to improve reuse of code and allow the use of existing libraries.
We installed it by doing:
npm install xmlhttprequest
Then in the tests code we just had to add:
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
jQuery.support.cors = true;
jQuery.ajaxSettings.xhr = function () {
    return new XMLHttpRequest;
}
and our tests started to run.