5

I'm testing a function that makes AJAX requests, allowing retries when the network is not working or there are timeouts because the connection is unstable (I'm thinking about mobile devices).

I'm sure it works because I've used it integrated with other code, but I want to have a proper test.

However I haven't been able to create a unit test to ensure it formally. I'm using jasmine 2.3 along with karma and here is my code so far:

var RETRIES=2;
var TIMEOUT=1000;

function doRequest(method, url, successFn, errorFn, body, retries) {
    var request = new XMLHttpRequest();
    retries = retries === undefined ? RETRIES : retries;
    request.open(method, url);
    request.setRequestHeader('Accept', 'application/json');
    request.setRequestHeader('Content-Type', 'application/json');

    request.onload = function () {
        if (request.status < 300 && request.status >= 200) {
            successFn(JSON.parse(request.responseText));
        } else {
            if (errorFn) {
                errorFn(this.status, this.responseText);
            }
        }
    };
    request.onerror = function () {
        if (this.readyState === 4 && this.status === 0) {
        //there is no connection
            errorFn('NO_NETWORK'); 
        } else {
            errorFn(this.status, this.responseText);
        }
    };

    if (retries > 0) {
        request.ontimeout = function () {
            doRequest(method, url, successFn, errorFn, body, retries - 1);
        };
    } else {
        request.ontimeout = function () {
            errorFn('timeout');
        };
    }
    request.timeout = TIMEOUT;
    if (body) {
        request.send(body);
    } else {
        request.send();
    }
}

And this is my test:

describe('Ajax request', function () {
    'use strict';

    var RETRIES=2;
    var TIMEOUT=1000;

    beforeEach(function () {
        jasmine.Ajax.install();
        jasmine.clock().install();

    });

    afterEach(function () {
        jasmine.Ajax.uninstall();
        jasmine.clock().uninstall();
    });


    it(' should call error callback function when a tiemout happens', function () {
        var doneFn = jasmine.createSpy('onLoad');
        var errorFn=jasmine.createSpy('onTimeout');
        doRequest('GET','http://www.mockedaddress.com', doneFn, errorFn,null,RETRIES);
        var request = jasmine.Ajax.requests.mostRecent();

        expect(request.method).toBe('GET');
        jasmine.clock().tick(TIMEOUT*(RETRIES+1)+50); //first attempt and then 2 retries
        expect(errorFn).toHaveBeenCalled(); // assertion failed
    });

});

And this is the output of the test:

    Expected spy onTimeout to have been called.
        at Object.<anonymous> (js/test/doRequest_test.js:75:0)
Chrome 42.0.2311 (Windows 7): Executed 1 of 1 (1 FAILED) ERROR (0.007 secs / 0.008 secs)
Pablo Lozano
  • 10,122
  • 2
  • 38
  • 59

1 Answers1

3

Use .responseTimeout on a mocked request object, this method is provided by jasmine-ajax, but is not documented.

jasmine.Ajax.requests.mostRecent().responseTimeout();

But after that, calling .responseTimeout on the first request, you still have an error, because the errorFn callback fires only after several retries. You have to deal with the next requests which are being sent automatically by request.ontimeout defined in your code. It can be solved like that:

var retries = 3;
var request;

do {
  request = jasmine.Ajax.requests.mostRecent();

  request.responseTimeout();

  retries -= 1;

} while(retries >= 0);

As a result, it will call a timeout for each subsequent request.

Here is some sandbox to play with the code http://plnkr.co/edit/vD2N66EwkpgiLFpg1Afc?p=preview

Michael Radionov
  • 12,859
  • 1
  • 55
  • 72
  • I'm also upvoting this answer for finding the not documented method. I started to browse the jasmine-ajax plugin when I asked the question but I could not find the solution by myself – Pablo Lozano May 18 '15 at 07:58
  • 1
    Thanks for digging this out of the source code! Note for others: if you get an error like `Mock clock is not installed, use jasmine.clock().install()`, simply do what it says -- `install()` the clock in `beforeEach` and `uninstall()` it in `afterEach`. – Vicky Chijwani Feb 01 '16 at 08:57