How do I verify jQuery AJAX events with Jasmine? - javascript

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.

I guess there are two types of tests you can do:
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.
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
});
}

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.

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();
});
});

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

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.

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

Related

imbricate two async calls

I am calling the twitch TV API to get users info from one endpoint and I want also to call another endpoint in the same API to check if those users are streaming live or not, but only if the first ajax call is successful. Can anyone give me a hint on how to do it?? My first call below:
var getUserInfo = $.ajax({
type: "GET",
url: "https://api.twitch.tv/helix/users/?login=ESL_SC2&login=freecodecamp&login=noobs2ninjas",
// contentType: ('application/x-www-form-urlencoded; charset=UTF-8'),
crossDomain: true,
headers: {
"Client-ID": "5k4g3q59o69v6p9tudn39v50ro1mux",
},
dataType: "json",
success: function (json) {
console.log(JSON.stringify(json, null, 2));
},
error: function () {
console.log("OOPS!!!");
},
})
The jQuery ajax function is built using the callback design to deal with its asynchronicity. You have two callbacks, success and error, one of which will fire when you receive a response from the Twitch API. If you want to make another ajax request depending on if your previous request was successful then you can simply write a very similar ajax request inside your success callback function pointing to the new location you are trying to acccess. I would recommend splitting that off into a separate function to maintain the readability of your code however.
The success function only runs if the call was successful.
Trigger the code for the second request from the success function.

JQuery POST aborted after page redirect

I have JQuery ajax POST operation which saves some data to a database. The operation takes about less than 20 milliseconds. This piece of javascript is embedded inside a .NET Web Forms application (out of my control). This .NET application triggers my POST inside a submit button and redirects to a another page. Now for the following browsers the POST is never executed:
FF on Windows
Safari on MAC
Chrome on MAC
IE works fine and even Chrome on Windows.
So I did some tests and tried to mimic the .NET Web Forms website which I luckily could reproduce by adding 'window.location = "go to another site"' directly after my save action. Here is my (stripped) code:
//BUTTON
$('#someSaveButton').click(function () {
GlobalObject.save();
});
// GLOBAL OBJECT CONTAINING ALL MY ACTIONS (delete, check, save, get)
var GlobalObject = (function () {
return {
save: function () {
...
someMode.save(); //NOTE: Depending on an url parameter different save methods are supported
window.location.href = "http://www.google.com"; //MIMIC-ING REQUEST ABORTING
}
};
})();
//EVENTUALLY THE SAVE:
function save() {
$.ajax({
type: "POST",
cache: false,
url: ...,
data: ...,
contentType: "application/json",
dataType: "json",
success: function (response) {
...
},
error: function (msg) {
...
}
});
}
This above reproduces the same POST-aborting behaviour. NOTE: the browser debugger reaches the $.ajax statement but never returns into 'success' or 'error'.
SOUTION I FINALLY FOUND:
I added 'async : false,' to the ajax call and everything seems to work fine!
Now I want to be sure if this is a correct solution because when I read on the JQuery site:
As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success().
I get a feeling the 'async: false' is maybe not a good solution. Is my Javascript construction OK? My background is C# and I am still learning/struggling on how to correctly embed/use functions inside javascript class definitions. So maybe my errors are related to a wrong construction of my functions (and the javascript garbage collector is cleaning things up or something).
alternatively, instead of settings async as false, you can do the redirect (window.location.href = "http://www.google.com";) within the success callback of the $.ajax call (and leave async: true):
//BUTTON
$('#someSaveButton').click(function () {
GlobalObject.save();
});
// GLOBAL OBJECT CONTAINING ALL MY ACTIONS (delete, check, save, get)
var GlobalObject = (function () {
return {
save: function () {
// ...
someMode.save(); //NOTE: Depending on an url parameter different save methods are supported
}
};
})();
//EVENTUALLY THE SAVE:
function save() {
$.ajax({
type: "POST",
cache: false,
url: ...,
data: ...,
contentType: "application/json",
dataType: "json",
success: function (response) {
// do stuff
// more stuff
// ... redirect when completed stuff
window.location.href = "http://www.google.com"; //MIMIC-ING REQUEST ABORTING
},
error: function (msg) {
// ...
}
});
}
hope that helps.
Your use of async: false is justified and correct in this instance.
The deprecation note refers to setting callback handlers on the jqXHR object, but you're already supplying them through the options object anyway.
Using async: false in this situation would not be incorrect, since the page is closing anyway and the user wouldn't expect it to be responsive, so the execution blocking goes mostly unnoticed (aside from the fact that 20 milliseconds are near inconceivable).
From the information provided, I don't see a cleaner solution than this.

Delay between repeated AJAX call to a web method

I have a ASP.Net web app with jquery implemented on the client side. within a while loop, the client side jquery script makes an asynchronous call to a web method in the server side code. The call returns the status of a long running server side task which jquery uses to update the user. The goal is to have jquery repeatedly call the server until the status is complete, once done it breaks out of the while loop and notifies the user thatthe task is complete.
My problem is that the below code runs in a while loop, but I want to make it delay or sleep between each call in order to prevent overwhelming the server with status requests. I tried calling setTimeout in the code below, but it only works with the initial ajax call, every subsequent call occurs back to back. Is there a way to efficiently delay each subsequent call to the server? Is this the most efficient way to achieve the kind of behavior I'm describing? Ideally I'd like a 2-5 second delay between each call.
I have the following code
Client Jquery:
var alertTimerId;
$('input[name=btnStatus]').click(function () {
var result = false;
//Loop while server reports task complete is true
while (!result) {
alertTimerId = setTimeout(function () {
$.ajax({
type: "POST",
url: "Default.aspx/GetStatus",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
async: true,
success: function (msg) {
if(msg.d != false)
result = msg.d;
},
error: function (xhr, ajaxOptions, thrownError) {
alert('AJAX failure');
}
});
}, 2000);
if (count > 0) {
count--;
}
}
});
Server Side ASP
[System.Web.Services.WebMethod]
public static bool GetStatus()
{
return result;
}
How about something like the following? The idea is that the Ajax call is encapsulated in a function, doAjax(), and then from within the Ajax success handler if the result is false you use setTimeout() to queue up another call to doAjax, otherwise you take whatever action you want to take for a true result. (You could optionally call doAjax() from the Ajax error handler too.)
$('input[name=btnStatus]').click(function () {
function doAjax() {
$.ajax({
type: "POST",
url: "Default.aspx/GetStatus",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
async: true,
success: function (msg) {
if (!msg.d)
setTimeout(doAjax, 2000);
else {
// Success! Notify user here
}
},
error: function (xhr, ajaxOptions, thrownError) {
alert('AJAX failure');
}
});
}
doAjax();
});
(Note: I've removed the if statement with count, since it seemed to have no relevance to the question. If your real code uses it just update it within the Ajax success handler too.)
Underscore.Js has a .throttle() helper function:
throttle_.throttle(function, wait)
Creates and returns a new, throttled version of the passed function, that, when invoked
repeatedly, will only actually call the original function at most once
per every wait milliseconds. Useful for rate-limiting events that
occur faster than you can keep up with.
var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);

ajax calls to controllers. How to indicate success, complete, and error

If I make an ajax call to a controller.... what needs to happen in the controller so that the ajax call then calls
1) complete:
2) success:
3) error:
4) any other callbacks that exist.
For ex. I have this ajax call.
$.ajax({
url: "/ContactPartial/ContactUs",
type: "POST",
data: JSON.stringify(data),
dataType: 'json',
contentType: "application/json; charset=utf-8",
complete: function () { },
success: function () { },
error: function () { }
});
In other words, what can I do inside /ContactPartial/ContactUs to control which of the 3 (complete,success,error) gets called after the controller code executes.
Also, how is this related to related to return Json(new {some: data});
These three callbacks are related to the status of the Ajax call. These are called depending on success of the call. For complete details refer to the documentation
So, if the server responds with a success (200), then both the Success and the Complete handlers would be called. In the complete handler, you might put some code to dismiss a modal window (regardless of success or error), and in the success function, you might put code to let the user know the call was successful, reload a grid view, etc. Also, keep in mind that the callback functions don't have to be anonymous functions, they can be defined functions that are shared among several Ajax calls.
EDIT:
If you are wanting to force the server to generate an error, take a look at:
The HttpResponse class, specifically the StatusCode property
This SO post explains more too (generating a 401 error)

$.ajax call with async:false and timeout expected

I've a web application which calls a webservice with ajax. This webservices returns me the configuraiton for my app, so the ajax call needs to be set at {async:false}.
Everything works fine when my server is up, but in the case it's not, my browser just freezes. Even if I set a timeout in my ajax call setup.
I've a error handler function which is never called even if the timeout is passed.
Someone has ever been confronted to that situation ?
#Edit : My problem can be solved by doing asynchronous call
As the browser is single threaded it would be better to process the config
returned in the callback success, and handle any errors in the error callback.
$.ajax({
url: "mydomain.com/url",
type: "GET",
dataType: "json",
data: $.param( $("Element or Expression") ),
complete: function() {
//called when complete
},
success: function() {
//called when successful
},
error: function() {
//called when there is an error
},
});

Categories

Resources