How to properly unit test jQuery's .ajax() promises using Jasmine and/or Sinon?

Unit TestingJqueryJasmineSinon

Unit Testing Problem Overview


I've got a fairly straightforward function which returns a jQuery .ajax() promise as such:

CLAW.controls.validateLocation = function(val, $inputEl) {
    return $.ajax({
        url: locationServiceUrl + 'ValidateLocation/',
        data: {
            'locationName': val
        },
        beforeSend: function() {
            $inputEl.addClass('busy');
        }
    }).done(function(result) {
        // some success clauses
    }).fail(function(result) {
        // some failure clauses
    }).always(function() {
        // some always clauses
    });
}

For the most part, this new promises interface works like a dream, and eliminating callback pyramids when using jQuery's .ajax() is great. However, I cannot for the life of me figure out how to properly test these promises using Jasmine and/or Sinon:

  1. All of Sinon's documentation assumes you're using old-school callbacks; I don't see a single example of how to use it with promises/deferreds

  2. When attempting to use a Jasmine or Sinon spy to spy on $.ajax, the spy is effectively overwriting the promise, so its done, fail, and always clauses no longer exist on the ajax function, so the promise never resolves and tosses an error instead

I'd really just love an example or two of how to test these new jQuery .ajax() promises with the aforementioned testing libs. I've scoured the 'net fairly intensely and haven't really dredged up anything on doing so. The one resource I did find mentioned using Jasmine.ajax, but I'd like to avoid that if possible, seeing as Sinon provides most of the same capabilities out-of-the-box.

Unit Testing Solutions


Solution 1 - Unit Testing

It is not that complex actually. It suffices to return a promise and resolve it according to your case.

For example:

spyOn($, 'ajax').andCallFake(function (req) {
    var d = $.Deferred();
    d.resolve(data_you_expect);
    return d.promise();
});

for a success, or

spyOn($, 'ajax').andCallFake(function (req) {
    var d = $.Deferred();
    d.reject(fail_result);
    return d.promise();
});

for a failure.

For Jasmine 2.0 the syntax has changed slightly:

spyOn($, 'ajax').and.callFake(function (req) {});

the method .andCallFake() does not exist in Jasmine 2.0

Solution 2 - Unit Testing

something along these lines / with sinon and jQuery deferreds

ajaxStub = sinon.stub($, "ajax");

function okResponse() {
  var d = $.Deferred();
  d.resolve( { username: "testuser", userid: "userid", success: true } );
  return d.promise();
};

function errorResponse() {
 var d = $.Deferred();
 d.reject({},{},"could not complete");
 return d.promise();
};

ajaxStub.returns(okResponse());
ajaxStub.returns(errorResponse());

Solution 3 - Unit Testing

Here's a simpler approach with just javascript.

quoteSnapshots: function (symbol, streamId) {
                var FakeDeferred = function () {
                    this.error = function (fn) {
                        if (symbol.toLowerCase() === 'bad-symbol') {
                            fn({Error: 'test'});
                        }
                        return this;
                    };
                    this.data = function (fn) {
                        if (symbol.toLowerCase() !== 'bad-symbol') {
                            fn({});
                        }
                        return this;
                    };
                };

                return new FakeDeferred();
            }

The if statements inside of each callback are what I use in my test to drive a success or error execution.

Solution 4 - Unit Testing

The solution given by @ggozad won't work if you use things like .complete().

But, hooray, jasmine made a plugin to do exactly this: http://jasmine.github.io/2.0/ajax.html

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

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

//in your tests
expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url');

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJ. Ky MarshView Question on Stackoverflow
Solution 1 - Unit TestingggozadView Answer on Stackoverflow
Solution 2 - Unit TestingyxaView Answer on Stackoverflow
Solution 3 - Unit TestingsamView Answer on Stackoverflow
Solution 4 - Unit TestingdamioView Answer on Stackoverflow