Using jqMock to test AJAX calls

Anastasia Cheetham a.cheetham at utoronto.ca
Thu Jul 30 17:47:36 UTC 2009


Unit testing AJAX calls is troublesome, because an AJAX call is  
typically used to communicate with a server, and when unit testing,  
you usually don't usually have a server to test against.

jqMock (http://jqmock.googlecode.com/svn/trunk/docs/userguide.html )  
is a jqUnit-based testing framework that can help with this. I have  
started to use it a little bit in CollectionSpace, and thought I'd  
share what I've learned so far, in case it helps anyone.

How can jqMock help with AJAX testing?
--------------------------------------

jqMock can be used to intercept calls to a function that is out of  
your control, and verify that you sent it the expected parameters. So  
you can use it to intercept calls to jQuery.ajax(), for example, and  
at the very least verify that your code constructed the options block  
properly. You don't have much control over what the server does with  
your request, so really, this is about all you can test anyway.


How do I add jqMock to the testing framework?
---------------------------------------------

jqMock was written to work with the version of jqUnit that is in the  
Google repository. The version we have in Infusion has changed, so I  
did have to add one extension to our jqUnit to make it work: jqMock  
calls jqUnit's "ok()" function, which calls qUnit's "ok()", and which  
we removed from jqUnit. When I added it back, all was fine. (NOTE: I  
made this mod in CollectionSpace, not in Fluid's trunk.)

The documentation for jqMock suggests calling jqMock.addShortcut(),  
but I couldn't get that to work. That simply meant that instead of  
using  'is' and 'expect', you have to use 'jqMock.is' and  
'jqMock.expect'


How do I set up a test for an ajax call using jqMock?
-----------------------------------------------------

There are 5 basic steps to this process:
1) Create the mock object for the ajax call
2) Set your expectations
3) Execute your tests
4) Verify that the expectations were met
5) Release the ajax interception

1) Create the mock object for the ajax call:

    var ajaxMock = new jqMock.Mock(jQuery, "ajax");  // will intercept  
calls to the "ajax" function of jQuery

2) Set your expectations:

    ajaxMock.modify().args(<object matching what you expect the  
arguments to the ajax call to be>);

3) Execute your tests:

    Obviously, this depends on your code. In CollectionSpace, I'm  
testing the DataContext's update call, which builds up the parameters  
to an ajax call, so it looks like this:

     testContext.update(modelPath);

4) Verify that the expectations were met:

     ajaxMock.verify();

5) Release the ajax interception:

     ajaxMock.restore();   // this prevents your mock object from  
intercepting any more ajax calls


Notes
-----

1) You can set up multiple expectations on a single mock object,  
roughly corresponding to multiple test calls. So in CollectionSpace,  
if I want to test my update() function with different model paths, I  
might have something like this:

     ajaxMock.modify().args(expectedParams_1);
     ajaxMock.modify().args(expectedParams_2);
     ajaxMock.modify().args(expectedParams_3);
     testContest.update(testModelPath_1);
     testContest.update(testModelPath_2);
     testContest.update(testModelPath_3);
     ajaxMock.verify();

Note that there is only one verify() regardless of the number of  
expectations. In the results, this will be treated as one test, with  
multiple sub-results.

2) Event handlers

I haven't figured out how to verify functions (if you even can), so  
the 'success' and 'error' options to the ajax call are troublesome.  
For now, you can verify a subset of the fields in an object using  
something called "is.objectThatIncludes()" in your expectation. You  
can use this to verify the subset of object fields that *can* be  
verified, for example:

     var expectedParams = {
         url: theTestUrlIExpectMyAppToCreate,
         type: "GET",
         dataType: "json",
         data: JSON.stringify(myTestData)
     }
      
ajaxMock.modify().args(jqMock.is.objectThatIncludes(expectedParams));

This will check the four fields I specified against the actual options  
block, but ignore the rest.

3) Results display:

- A failure report shows the expected value, but doesn't include the  
*actual* wrong value, which makes diagnosing the error harder. I've  
filed an issue requesting this enhancement with the project.

- In FF3 on Mac OS X at least, there is a bug in the display of the  
failure report that makes it difficult to read, but it's still  
possible. I've filed an issue with the project about this.



I hope this all makes some sense. Please, if you do try to use jqMock  
and this doesn't make sense, just ask.

-- 
Anastasia Cheetham                   a.cheetham at utoronto.ca
Software Designer, Fluid Project    http://fluidproject.org
Adaptive Technology Resource Centre / University of Toronto




More information about the fluid-work mailing list