Sunday, March 14, 2010

Ajax mock

One thing missing in QUnit and other javascript frameworks that I was looking for is mocking. Mock is object that replaces some complicated system, to simplify tests.
For example, I want to test javascript that I have ajax calls.

function serverSideSum(v1, v2) {
  $.post('http://localhost/sum', {val1: v1, val2: v2}, function(data) {
    $('#result').text(data.result);
  });
}

Normally, to make them work I have to run server that would answer to my calls. There is a number of problems
  • It is much slower compared to normal javascript tests
  • It requires infrastructure
  • How to check that my javascript sends correct data?
  • How to validate response or be sure that server part is not broken
Other option is to replace calls to real server with artificial function calls that always returns good results.
  • It is fast
  • Runs without server
  • Can check input data
  • Server response is always as good as you need

It is very easy to create mock in javascript. You just have to redefine some function. For example in JQuery ajax calls are sent by calling $.post() and $.get()
methods. So mock can be:

$.post = function(url, data, func) {
  func({result:4});
};



We can check parameters too:

$.post = function(url, data, func) {
  equals(data.val1, 2);
  equals(data.val2, 2);
  func({result:4});
};

And complete test looks like:

test("server side 2+2=4", function() {
  $.post = function(url, data, func) {
    equals(data.val1, 2);
    equals(data.val2, 2);
    func({result:4});
  };
  serverSideSum(2, 2);
  equals(4, $('#result').text());
});

Nice, but what if there are several calls to same ajax method during one function invocation?
For example, our function will also get number of calls to server:

function serverSideSum(v1, v2) {
  $.post('http://localhost/sum', {val1: v1, val2: v2}, function(data) {
    $('#result').text(data.result);
  });
  $.post('http://localhost/counter', {}, function(data) {
    $('#counter').text(data.count);
  });
}

Finally we can update our test to something like:

test("server side 2+2=4", function() {
  ajaxMock([
    [{result:4}, function(data) {equals(data.val1, 2); equals(data.val2, 2);}],
    [{count:123}, function(data) {}]
  ]);
  serverSideSum(2, 2);
  equals(4, $('#result').text());
  equals(123, $('#counter').text());
});


Function ajaxMock has one parameter - array of objects, and every object represents one ajax call and consists of call results and function thatvalidates input parameters.
Implementation of this method can be something like this:

function ajaxMock(params) {
  ajaxCallsCounter = 0;
  ajaxCallsParams = params;
  var callback = function(url, data, func) {
    ajaxCallsParams[ajaxCallsCounter][1](data);
    func(ajaxCallsParams[ajaxCallsCounter][0]);
    ajaxCallsCounter++;
  }
  $.get = callback;
  $.post = callback;
}

All ajax calls are stored in memory. After ajax call, array index ajaxCallsCounter will be increased and will not be called again, so this ensures that all ajax calls are done only once and in correct order.
Additionaly, you can verify that all calls were made by comparing array index with it's length, like this:

equals(ajaxCallsCounter, ajaxCallsParams.length);

No comments:

Post a Comment