40

I am writing some QUnit tests for a JavaScript that makes AJAX calls.

For isolation I overwrite $.ajax to write the parameter array of an AJAX call to a variable. This works to test how methods use AJAX functions, but I have difficulty testing the success handler of $.load()

From the documentation at http://api.jquery.com/load/:

When a successful response is detected (i.e. when textStatus is "success" or "notmodified"), .load() sets the HTML contents of the matched element to the returned data.

So I have attempted to return an object containing objects with the same name as variables for the success handler:

    //Mock ajax function
    $.ajax = function (param) {
        _mockAjaxOptions = param;
        var fakeAjaxSuccess = { responseText: "success", textStatus: "success", XMLHttpRequest: "success" };
        return fakeAjaxSuccess;
    };

But this approach hasn't worked.

How can I replicate the behaviour of a successful AJAX call?

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
StuperUser
  • 10,555
  • 13
  • 78
  • 137

7 Answers7

29

This question has a few years and for the new versions of jQuery has changed a bit.

To do this with Jasmin you can try Michael Falaga's approach

Solution

  function ajax_response(response) {
    var deferred = $.Deferred().resolve(response);
    return deferred.promise;
  }

With Jasmine

  describe("Test test", function() {
    beforeEach(function() {
      spyOn($, 'ajax').and.returnValue(
        ajax_response([1, 2, 3])
      );
    });
    it("is it [1, 2, 3]", function() {
      var response;
      $.ajax('GET', 'some/url/i/fancy').done(function(data) {
        response = data;
      });
      expect(response).toEqual([1, 2, 3]);
    });
  });

No Jasmine

  $.ajax = ajax_response([1, 2, 3]);
  $.ajax('GET', 'some/url/i/fancy').done(function(data) {
     console.log(data); // [1, 2, 3]
  });
 
Weston Ganger
  • 6,324
  • 4
  • 41
  • 39
user1634074
  • 681
  • 8
  • 17
  • +one Thank you very much. – whitesiroi Dec 18 '15 at 05:29
  • I get `ajax is not a function` is: `{state: ƒ, always: ƒ, catch: ƒ, pipe: ƒ...` – Toskan May 05 '20 at 05:18
  • 1
    @Toskan I had the same problem. I assume this answer was correct in 2015, but as of my writing today the "return deferred.promise();" line returns an OBJECT. You want to change this to "return deferred.promise;", so it returns the promise FUNCTION. – David Alan Condit Jun 01 '20 at 18:06
  • do not use deferred: https://stackoverflow.com/questions/63783936/keep-getting-error-deferred-is-not-a-function – Jiří Jan 06 '22 at 15:47
15

After reading inspired by @Robusto and @Val, I found a method that works:

//Mock ajax function
$.ajax = function (param) {
    _mockAjaxOptions = param;
    //call success handler
    param.complete("data", "textStatus", "jqXHR");
};

Instead of raising the event from any real $.ajax code or by triggering any events, I have my fake ajax object call the function (which is passed in as a parameter to $.ajax()) as part of my fake function.

StuperUser
  • 10,555
  • 13
  • 78
  • 137
  • I would have done the same thing although I hid the $.ajax call behind another method to make it easier to swap out in my tests. – Peter Smith Jun 19 '12 at 12:48
  • 2
    Although it's maybe late, but you might have been looking for https://github.com/appendto/jquery-mockjax – xhh Sep 02 '12 at 09:45
  • @xhh that's a pretty good framework for setting up return values. – StuperUser Sep 03 '12 at 08:59
  • @PeterSmith why did you add another function? Since JS is dynamically typed you can just overwrite it in situ. Did it give you any benefits other than encapsulation? – StuperUser Sep 03 '12 at 09:00
  • 2
    And what exactly do you do with this `_mockAjaxOptions` undeclared variable that seems to hang in the air? – vsync Mar 26 '14 at 14:30
  • If I recall correctly, I declared `_mockAjaxOptions` outside of this and inspected it in the assert step of a test. – StuperUser Mar 26 '14 at 14:31
  • Is there any way to do this with Sinon? – frogbandit Jun 21 '16 at 19:34
  • @frogbandit A lot's changed in two years and lot of ajax is baked into js frameworks, that would be a good question independently – StuperUser Jun 23 '16 at 10:42
  • @xhh that's a pretty neat framework, maybe its a good contemporary answer instead of getting lost in a comment (I don't mind posting it myself if ur busy) – msanjay Dec 07 '17 at 08:24
12

Use a closure to override $.ajax with a dummy response

After trying the accepted answer and the answer posted by user1634074, I devised this simple and flexible blend of the two.

In its most basic form…

function ajax_response(response) {
  return function (params) {
    params.success(response);
  };
}
$.ajax = ajax_response('{ "title": "My dummy JSON" }');

In the above example, define a function ajax_response() that accepts some JSON string as an argument (or any number of custom arguments useful for simulating a response) and returns an anonymous closure function that will be assigned to $.ajax as an override for unit testing.

The anonymous function accepts a params argument which will contain the settings object passed to the $.ajax function. And it uses the argument(s) passed to the outer function to simulate a response from the server. In this example, it always simulates a successful response from the server, by simply invoking the success callback and supplying it with the dummy JSON.

It is easy to reconfigure…

function ajax_response(response, success) {
  return function (params) {
    if (success) {
      params.success(response);
    } else {
      params.error(response);
    }
  };
}

// Simulate success
$.ajax = ajax_response('{ "title": "My dummy JSON." }', true); 
doAsyncThing(); // Function that calls $.ajax

// Simulate error
$.ajax = ajax_response('{ "error": "Who is the dummy now?" }', false); 
doAsyncThing(); // Function that calls $.ajax

Below we can see it in action…

/* FUNCTION THAT MAKES AJAX REQUEST */
function doAsyncThing() {
  $.ajax({
    type: "POST",
    url: "somefile.php",
    // data: {…},
    success: function (results) {
      var json = $.parseJSON(results),
          html = $('#ids').html();
      $('#ids').html(html + '<br />' + json.id);
    }
  });
}

/* BEGIN MOCK TEST */
// CREATE CLOSURE TO RETURN DUMMY FUNCTION AND FAKE RESPONSE
function ajax_response(response) {
  return function (params) {
    params.success(response);
  };
}

var n = prompt("Number of AJAX calls to make", 10);

for (var i = 1; i <= n; ++i) {
  
  // OVERRIDE $.ajax WITH DUMMY FUNCTION AND FAKE RESPONSE
  $.ajax = ajax_response('{ "id": ' + i + ' }');
  doAsyncThing();
}
/* END MOCK TEST */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="ids">IDs:</p>
Community
  • 1
  • 1
gfullam
  • 11,531
  • 5
  • 50
  • 64
6

Mock $.ajax as needed without disturbing jQuery

The answers here are good but had a specific need to build out a fake response to a single API call while leaving all other API calls the same until the backend service was built out so I can continue building stuff on the UI.

The API object uses $.ajax under the hood so you can call an API method like so:

api.products({ price: { $lt: 150, tags: ['nike', 'shoes'] } })
.done(function(json) {
  // do something with the data
})
.error(function(err) {
  // handle error
});

This method does the trick:

function mockAjax(options) {
  var that = {
    done: function done(callback) {
      if (options.success)
        setTimeout(callback, options.timeout, options.response);
      return that;
    },
    error: function error(callback) {
      if (!options.success)
        setTimeout(callback, options.timeout, options.response);
      return that;
    }
  };
  return that;
}

Then override a single api call without touching $.ajax:

api.products = function() {
  return mockAjax({
    success: true,
    timeout: 500,
    response: {
      results: [
        { upc: '123123', name: 'Jordans' },
        { upc: '4345345', name: 'Wind Walkers' }
      ]
    }
  });
};

https://jsfiddle.net/Lsf3ezaz/2/

Community
  • 1
  • 1
King Friday
  • 25,132
  • 12
  • 90
  • 84
1

Look at the jQuery documentation: You'll see that the Ajax setup provides a number of other conditions that are tested for. If you make them all point to your fakeAjaxSuccess, you might achieve for your objective.

Alternatively, wrap your $.ajax call into its own function and have whatever calls it simply call your event handler with the fakeAjaxSuccess object.

Robusto
  • 31,447
  • 8
  • 56
  • 77
1

I think the link below should help. as for a parameter I am not so sure but it could be .

$.fn.ajax.success =  function (){
  ///the rest goest here
}

Override jQuery .val() function?

Community
  • 1
  • 1
Val
  • 17,336
  • 23
  • 95
  • 144
  • doesn't make any sense...the browser would still do a request. this only overrides the success function it seems. – vsync Mar 26 '14 at 14:31
1

Here is a simple working solution

var set_ajax_response = function(data){
  $.ajax = $.Deferred().resolve(data).promise;
}

var data = [1,2,3];

set_ajax_response(data);
Weston Ganger
  • 6,324
  • 4
  • 41
  • 39
  • do not use deferred: https://stackoverflow.com/questions/63783936/keep-getting-error-deferred-is-not-a-function – Jiří Jan 06 '22 at 15:48