Using Spys to return fake AJAX data with Backbone

3,348 views
Skip to first unread message

Phil Cockfield

unread,
Mar 24, 2011, 12:04:26 AM3/24/11
to Jasmine
Does anyone know how you might use the Jasmine spys to return fake
server responses for Backbone models (http://documentcloud.github.com/
backbone/).

There's an article (here: http://tinnedfruit.com/2011/03/03/testing-backbone-apps-with-jasmine-sinon.html)
that talks about doing this using

Jasmine + Backbone + Sinon

He does all the interception with Sinon - so I wonder, is this just
not possible with Jasmine right now? Or is there a way to get in and
do it. I saw Davis Frank talking on this video (http://
www.viddler.com/explore/saucelabs/videos/32/18.005/) talking about
intercepting AJAX calls, so I figure this is what Jasmine spying is at
least partly about.

Thanks everyone!

Davis Frank

unread,
Mar 24, 2011, 12:26:39 AM3/24/11
to jasmi...@googlegroups.com, Phil Cockfield
I read that as well. I'm not sure why he insisted on using Sinon instead of Jasmine spies.

Please check out the jasmine-ajax project: https://github.com/pivotal/jasmine-ajax

This stubs out AJAX calls at the XHR level. This should work below anything, really.  It currently supports jQuery and Prototype. But I expect that Backbone support will come from someone soon-ish.

And yes, the next time I give a Jasmine talk and refer to AJAX stubbing I'm going to use this code. :)

--dwf




--
You received this message because you are subscribed to the Google Groups "Jasmine" group.
To post to this group, send email to jasmi...@googlegroups.com.
To unsubscribe from this group, send email to jasmine-js+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/jasmine-js?hl=en.


Phil Cockfield

unread,
Mar 24, 2011, 8:00:14 PM3/24/11
to Jasmine
Thanks Davis!

I pulled down your jasmine-ajax library and started stepping through
how that works. Even though you don't support Backbone specifically,
the fact that you have jQuery hooks in there would be fine, because
Backbone defer to jQuery (if it's available).

However - for the record, now that I see what you guys are up to, I
figured out just how easy it is to setup spy's around Backbone.js
fetching. Basically you just do something like this:

it('returns fake AJAX data', function() {
spyOn(Backbone, 'sync').andCallFake(function(method, model,
success, error) {
success({ foo:'fake' });
});

// Pre-condition (default value).
expect(model.get('foo')).toEqual('bar');

// Cause AJAX call to be invoked.
model.fetch();

// Post-condition.
expect(model.get('foo')).toEqual('fake');
});


Backbone ferries all these AJAX requests through it's Backbone.sync
function - and in the source, even encourages you to override that -
so it's pretty easy. In the above code I'm returning the fake data I
want in a serial manner, so I don't even have to kludge up my tests
with dealing with an async callback.

See any problems with this approach. Seems to be in the same spirit
as the code I was looking at in the "jasmine-ajax" library.
--
Phil

Davis Frank

unread,
Mar 24, 2011, 8:10:52 PM3/24/11
to jasmi...@googlegroups.com, Phil Cockfield
What does Backbone use for XHRs? Does it have it's own wrapper?

I'm a lightweight Mockist - which means I want to have as few touchpoints to maintain as possible. Stubbing at XHR leads to less work when refactoring around your (in this case sync) calls.

Ideally the global beforeEach would detect if Backbone was loaded and spy appropriately so that you could use the existing helpers (mostRecentAjaxRequest, etc.).

I you can make it work, please fork jasmine-ajax and issue a pull request. The intern we had work on this last summer is starting full time in a couple of months and I'd love to throw these things at him then to process.

thx,
--dwf




--

Ben Loveridge

unread,
Mar 24, 2011, 8:16:25 PM3/24/11
to Jasmine
Phil,

I may be a bit of a heretic around here, but I use sinon.js for
mocking my ajax requests (sync and async) for similar reasons -- I can
tell it to respond serially without needing to use things like waits,
which I think make the tests harder to read.

I typically have something like this:

var server;
beforeEach(function(){
server = sinon.useFakeServer();
server.respondWith("GET", "/someUrl.json",
[200, { "Content-Type": "application/json" }, someJsonString]
);
});
afterEach(function(){
server.restore();
});

describe("someAsyncMethod", function(){
it("should load data asynchronously", function(){
var cb = jasmine.createSpy();
someAsyncMethodWhichInvokesCbOnCompetion(cb);
expect(cb).not.toHaveBeenCalled();

server.respond();
expect(cb).toHaveBeenCalledWith(someJsonString);
});
});

describe("someSyncMethod", function(){
it("should load data synchronously", function(){
var data = someSyncMethod();
expect(data).toEqual(someJsonString);
});
});

Note that in the second instance, I did not need to use server.respond
-- that is because sinon.js honors the async parameter of
XmlHttpRequest.open and immediately responds if appropriate, rather
than waiting for server.respond() to be called.

Using sinon in this way to test both my sync and async XHR has been
very nice, and my tests all feel very readable.

- Ben

Phil Cockfield

unread,
Mar 24, 2011, 8:40:51 PM3/24/11
to Jasmine
Davis,

Backbone ferries it all through Backbone.sync, which in turn defers to
jQuery. Basically it just does this:

REF: http://documentcloud.github.com/backbone/#Sync

Backbone.sync = function(method, model, success, error) {
var type = methodMap[method];
var modelJSON = (method === 'create' || method === 'update') ?
JSON.stringify(model.toJSON()) : null;

// Default JSON-request options.
var params = {
url: getUrl(model),
type: type,
contentType: 'application/json',
data: modelJSON,
dataType: 'json',
processData: false,
success: success,
error: error
};

// Make the request.
$.ajax(params);
};

-----------------

This, however, is not called by my code - it's an indirect result of
invoking "fetch()" on a model. And so following your examples in

https://github.com/pivotal/jasmine-ajax

I couldn't see how to cause the 'success' callback to be invoked via a
"fetch()". In the sample specs you have (namely TwitterApiSpec.js) I
see you're creating spys for each of the callbacks, and then checking
that they were invoked.

But how, using "jasmine-ajax", and that jQuery setup in the
beforeEach, would you plug the fake data in and cause the OnSuccess
callback to be invoked. Am I missing something obvious?

I guess I could intercept lower down at the "$.ajax(params);" level.

Thanks.
--
Phil




On Mar 24, 5:10 pm, Davis Frank <dwfr...@pivotallabs.com> wrote:
> What does Backbone use for XHRs? Does it have it's own wrapper?
>
> I'm a lightweight Mockist - which means I want to have as few touchpoints to
> maintain as possible. Stubbing at XHR leads to less work when refactoring
> around your (in this case sync) calls.
>
> Ideally the global beforeEach would detect if Backbone was loaded and spy
> appropriately so that you could use the existing helpers
> (mostRecentAjaxRequest, etc.).
>
> I you can make it work, please fork jasmine-ajax and issue a pull request.
> The intern we had work on this last summer is starting full time in a couple
> of months and I'd love to throw these things at him then to process.
>
> thx,
> --dwf
>

Phil Cockfield

unread,
Mar 24, 2011, 8:45:21 PM3/24/11
to Jasmine
Thanks Ben. Another vote for Sinon.

I'm not sure (given how easy Backbone is to intercept with Spy's) that
some version of my Jasmine spying approach won't be simple enough ...
but perhaps I should give Sinon.js another look.

I think I can create the beforeEach along the lines of what Davis is
talking about, so I'm going to plug at this a little.

Davis Frank

unread,
Mar 25, 2011, 12:57:26 AM3/25/11
to jasmi...@googlegroups.com, Phil Cockfield
The point of jasmine-ajax is to make testing your AJAX calls serial and controllable instead of using async spec (waitsFor/runs).


The flow is like this:
  • The global beforeEach installs the fake XHR, which stubs all AJAX calls
  • Each request can then be inspected for URL or params (see line 24)
  • Each request can then be passed a response fixture (see line 29) which then exercises the API object and any callbacks
It's totally our fault if this isn't obvious from the spec files - we need to blog and document this project better.

It's fine if you like Sinon better. But you don't need Sinon to remove waitsFor/runs calls from your specs when you're testing external calls vai AJAX.

You should only need waitsFor/Runs when you're testing you're own Asynchronous interfaces.

Does this make more sense now?

--dwf



Phil Cockfield

unread,
Mar 26, 2011, 2:07:39 PM3/26/11
to Jasmine
BTW: The as I dig into working with jasmine-ajax further, I need to
clarify that most of my initial confusion/comments were because I was
working with jQuery 1.5.1, and the jasmine-ajax project is working
against 1.4.

There are some changes in the way jQuery handles AJAX in 1.5.1 - so
that's why things were confoundedly not working the way I thought they
were being explained in the project's samples. Turns out it does
work.

I have written some Backbone specs to prove it works as expected
(against jQuery 1.4) and have the pull request here if it's
interesting for you guys to look at and maybe include:
https://github.com/pivotal/jasmine-ajax/pull/7

I am now looking at what's different with jQuery 1.5. Basically the
issue is that it doesn't look like the 'success' (and probably
'error') callbacks are being called when

request.response(data) ;

is being invoked.



On Mar 24, 9:57 pm, Davis Frank <dwfr...@pivotallabs.com> wrote:
> The point of jasmine-ajax is to make testing your AJAX calls serial and
> controllable instead of using async spec (waitsFor/runs).
>
> Look at:https://github.com/pivotal/jasmine-ajax/blob/master/examples/jquery/s...
>
> The flow is like this:
>
>    - The global beforeEach installs the fake XHR, which stubs all AJAX calls
>    - Each request can then be inspected for URL or params (see line 24)
>    - Each request can then be passed a response fixture (see line 29) which
>    then exercises the API object and any callbacks
>
> It's totally our fault if this isn't obvious from the spec files - we need
> to blog and document this project better.
>
> It's fine if you like Sinon better. But you don't need Sinon to remove
> waitsFor/runs calls from your specs when you're testing external calls vai
> AJAX.
>
> You should only need waitsFor/Runs when you're testing you're own
> Asynchronous interfaces.
>
> Does this make more sense now?
>
> --dwf
>

Davis Frank

unread,
Mar 26, 2011, 2:11:18 PM3/26/11
to jasmi...@googlegroups.com, Phil Cockfield
sorry about that re: jQuery 1.5.x

We have some changes for jQ 1.5, but they've not made it into a pull request yet.  I need to go poke somebody about it.

Meanwhile, does the pattern make more sense now? How you don't need waitsFor/runs for AJAX calls?

--dwf

Phil Cockfield

unread,
Mar 26, 2011, 4:08:09 PM3/26/11
to Jasmine
Cool! Yeah, it makes total sense now. Thanks Davis.


On Mar 26, 11:11 am, Davis Frank <dwfr...@pivotallabs.com> wrote:
> sorry about that re: jQuery 1.5.x
>
> We have some changes for jQ 1.5, but they've not made it into a pull request
> yet.  I need to go poke somebody about it.
>
> Meanwhile, does the pattern make more sense now? How you don't need
> waitsFor/runs for AJAX calls?
>
> --dwf
>
Reply all
Reply to author
Forward
0 new messages