How do I verify jQuery AJAX events with Jasmine?

JavascriptJqueryAjaxJasmineBdd

Javascript Problem Overview


I am trying to use Jasmine to write some BDD specs for basic jQuery AJAX requests. I am currently using Jasmine in standalone mode (i.e. through SpecRunner.html). I have configured SpecRunner to load jquery and other .js files. Any ideas why the following doesn't work? has_returned does not become true, even thought the "yuppi!" alert shows up fine.

describe("A jQuery ajax request should be able to fetch...", function() {

  it("an XML file from the filesystem", function() {
    $.ajax_get_xml_request = { has_returned : false };  
    // initiating the AJAX request
    $.ajax({ type: "GET", url: "addressbook_files/addressbookxml.xml", dataType: "xml",
             success: function(xml) { alert("yuppi!"); $.ajax_get_xml_request.has_returned = true; } }); 
    // waiting for has_returned to become true (timeout: 3s)
    waitsFor(function() { $.ajax_get_xml_request.has_returned; }, "the JQuery AJAX GET to return", 3000);
    // TODO: other tests might check size of XML file, whether it is valid XML
    expect($.ajax_get_xml_request.has_returned).toEqual(true);
  }); 

});

How do I test that the callback has been called? Any pointers to blogs/material related to testing async jQuery with Jasmine will be greatly appreciated.

Javascript Solutions


Solution 1 - Javascript

I guess there are two types of tests you can do:

  1. Unit tests that fake the AJAX request (using Jasmine's spies), enabling you to test all of your code that runs just before the AJAX request, and just afterwards. You can even use Jasmine to fake a response from the server. These tests would be faster - and they would not need to handle asynchronous behaviour - since there isn't any real AJAX going on.
  2. Integration tests that perform real AJAX requests. These would need to be asynchronous.

Jasmine can help you do both kinds of tests.

Here is a sample of how you can fake the AJAX request, and then write a unit test to verify that the faked AJAX request was going to the correct URL:

it("should make an AJAX request to the correct URL", function() {
    spyOn($, "ajax");
    getProduct(123);
    expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/products/123");
});

function getProduct(id) {
    $.ajax({
        type: "GET",
        url: "/products/" + id,
        contentType: "application/json; charset=utf-8",
        dataType: "json"
    });
}

For Jasmine 2.0 use instead:

expect($.ajax.calls.mostRecent().args[0]["url"]).toEqual("/products/123");

as noted in this answer

Here is a similar unit test that verifies your callback was executed, upon an AJAX request completing successfully:

it("should execute the callback function on success", function () {
    spyOn($, "ajax").andCallFake(function(options) {
        options.success();
    });
    var callback = jasmine.createSpy();
    getProduct(123, callback);
    expect(callback).toHaveBeenCalled();
});

function getProduct(id, callback) {
    $.ajax({
        type: "GET",
        url: "/products/" + id,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: callback
    });
}

For Jasmine 2.0 use instead:

spyOn($, "ajax").and.callFake(function(options) {

as noted in this answer

Finally, you have hinted elsewhere that you might want to write integration tests that make real AJAX requests - for integration purposes. This can be done using Jasmine's asyncronous features: waits(), waitsFor() and runs():

it("should make a real AJAX request", function () {
    var callback = jasmine.createSpy();
    getProduct(123, callback);
    waitsFor(function() {
        return callback.callCount > 0;
    });
    runs(function() {
        expect(callback).toHaveBeenCalled();
    });
});

function getProduct(id, callback) {
    $.ajax({
        type: "GET",
        url: "data.json",
        contentType: "application/json; charset=utf-8"
        dataType: "json",
        success: callback
    });
}

Solution 2 - Javascript

Look at the jasmine-ajax project: http://github.com/pivotal/jasmine-ajax.

It's a drop-in helper that (for either jQuery or Prototype.js) stubs at the XHR layer so that requests never go out. You can then expect all you want about the request.

Then it lets you provide fixture responses for all your cases and then write tests for each response that you want: success, failure, unauthorized, etc.

It takes Ajax calls out of the realm of asynchronous tests and provides you a lot of flexibility for testing how your actual response handlers should work.

Solution 3 - Javascript

here is a simple example test suite for an app js like this

var app = {
               fire: function(url, sfn, efn) {
                   $.ajax({
                       url:url,
                       success:sfn,
                       error:efn
                   });
                }
         };


  

a sample test suite, which will call callback based on url regexp

describe("ajax calls returns", function() {
 var successFn, errorFn;
 beforeEach(function () {
    successFn = jasmine.createSpy("successFn");
    errorFn = jasmine.createSpy("errorFn");
    jQuery.ajax = spyOn(jQuery, "ajax").andCallFake(
      function (options) {
          if(/.*success.*/.test(options.url)) {
              options.success();
          } else {
              options.error();
          }
      }
    );
 });
 
 it("success", function () {
     app.fire("success/url", successFn, errorFn);
     expect(successFn).toHaveBeenCalled();
 });
 
 it("error response", function () {
     app.fire("error/url", successFn, errorFn);
     expect(errorFn).toHaveBeenCalled();
 });
});

Solution 4 - Javascript

When I specify ajax code with Jasmine, I solve the problem by spying on whatever depended-on function initiates the remote call (like, say, $.get or $ajax). Then I retrieve the callbacks set on it and test them discretely.

Here's an example I gisted recently:

https://gist.github.com/946704

Solution 5 - Javascript

Try jqueryspy.com It provides an elegant jquery like syntax to describe your tests and allows callbacks to test after the ajax has complete. Its great for integration testing and you can configure maximum ajax wait times in seconds or milleseconds.

Solution 6 - Javascript

I feel like I need to provide a more up-to-date answer since Jasmine is now at version 2.4 and a few functions have changed from the version 2.0.

So, to verify that a callback function has been called within your AJAX request, you need to create a spy, add a callFake function to it then use the spy as your callback function. Here's how it goes:

describe("when you make a jQuery AJAX request", function()
{
    it("should get the content of an XML file", function(done)
    {
        var success = jasmine.createSpy('success');
        var error = jasmine.createSpy('error');

        success.and.callFake(function(xml_content)
        {
            expect(success).toHaveBeenCalled();

            // you can even do more tests with xml_content which is
            // the data returned by the success function of your AJAX call

            done(); // we're done, Jasmine can run the specs now
        });

        error.and.callFake(function()
        {
            // this will fail since success has not been called
            expect(success).toHaveBeenCalled();

            // If you are happy about the fact that error has been called,
            // don't make it fail by using expect(error).toHaveBeenCalled();

            done(); // we're done
        });

        jQuery.ajax({
            type : "GET",
            url : "addressbook_files/addressbookxml.xml",
            dataType : "xml",
            success : success,
            error : error
        });
    });
});

I've done the trick for the success function as well as the error function to make sure that Jasmine will run the specs as soon as possible even if your AJAX returns an error.

If you don't specify an error function and your AJAX returns an error, you will have to wait 5 seconds (default timeout interval) until Jasmine throws an error Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. You can also specify your own timeout like this:

it("should get the content of an XML file", function(done)
{
    // your code
},
10000); // 10 seconds

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
QuestionmnacosView Question on Stackoverflow
Solution 1 - JavascriptAlex YorkView Answer on Stackoverflow
Solution 2 - Javascriptuser533109View Answer on Stackoverflow
Solution 3 - JavascriptskipyView Answer on Stackoverflow
Solution 4 - JavascriptJustin SearlsView Answer on Stackoverflow
Solution 5 - JavascriptSteve TomsView Answer on Stackoverflow
Solution 6 - JavascriptpmrotuleView Answer on Stackoverflow