I am trying to construct a Qunit test to test an ajax post call. I am using mockajax to mock the call. However, I can't get the test to work with the asynchronous call.
Here is my source code:
source.js:
ajaxFunc = function(element){
$.post({
type:'POST',
url: '/temp/',
dataType:'json',
success: function(data){
element.text("changed in func");
},
error: function(data){
//do something
},
timeout: 60000
});
}
syncFun = function(element){
element.text("changed in func");
}
Here is my test code:
tests2.js
function mockSuccess(){
$.mockjax({
url: "/temp/",
responseText: { success: true }
});
}
QUnit.test( "test ajax async", function( assert ) {
mockSuccess();
var done = assert.async();
var element = document.createElement("div");
ajaxFunc($(element));
$(element).text("old");
setTimeout(function() {
assert.strictEqual($(element).text(), 'changed in func');
done();
});
});
QUnit.test( "test sync", function( assert ) {
var element = document.createElement("div");
$(element).text("old");
syncFun($(element));
assert.strictEqual($(element).text(), 'changed in func');
});
The first tests fails, and the second one succeeds. If I put a comment in the source.js within the success callback, I see in fact that the the post succeeds and changes the elements text.
In other words, everything works correctly except testing the results of the post.
I have looked at https://qunitjs.com/cookbook/ and the examples on stack overflow with no luck.
It looks like I just need to add a number of milliseconds to get the async call to work:
QUnit.test( "test ajax async", function( assert ) {
mockSuccess();
var done = assert.async();
var element = document.createElement("div");
ajaxFunc($(element));
$(element).text("old");
setTimeout(function() {
assert.strictEqual($(element).text(), 'changed in func');
done();
}, 800);
// ^^^
});
I have seen examples like this in the github for qunit, so I guess this is correct, but I can see problems where you don't set the time function high enough.
I have already moved all of the processing that occurs in success: and error: outside the post function, so I can test it independently of the ajax calls, but I still wanted to test the actual post for various types of errors.
Related
I have the following function to check a users session to see if they're staff or not. Now, I know there are better ways to do this, but I'm trying to make a simple application that's tied with a forum software.
function isStaff(callback) {
$.ajax({
url: url
}).done(function(data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
callback(true);
} else {
callback(false);
}
});
}
Let's say I'm using this function in, like so, when compiling a "post" (Handlebars).
function compilePost(post) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: function() {
isStaff(function(response) {
return response;
});
}
}
var html= template(context);
return html;
}
Problem here, is that the request to check if a user is staff doesn't complete the request until after the function is ran.
I know with Promises is an alternative to async: false, where request is made and the response comes back before the function finishes.
But I have no idea how I can convert this into a promise. I've tried to learn it but I'm stuck at the concept. Can someone explain this to me? Thanks.
First, let's simplify the compilePost function. This function should know how to compile a post in a synchronous manner. Let's change the isStaff fetching to a simple argument.
function compilePost(post, isStaff) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: isStaff
}
var html= template(context);
return html;
}
Now, let's create a new method, with a single purpose - checking if a user is member of the staff:
function checkForStaffMemebership() {
return new Promise(function (resolve, reject) {
$.ajax({
url: url,
success: function (data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
resolve(true);
} else {
resolve(false);
}
}
});
});
}
This function wraps your original ajax call to the server with a promise, whenever the $.ajax call gets a response from the server, the promise will resolve with the answer whether the user is a staff member or not.
Now, we can write another function to orchestrate the process:
function compilePostAsync(post) {
return checkForStaffMemebership()
.then(function (isStaff) {
return compilePost(post, isStaff);
});
}
compilePostAsync finds out whether the user is a staff member or not. Then, it's compiling the post.
Please notice that compilePostAsync returns a promise, and thus if you used to have something like:
element.innerHTML = compilePost(post);
Now, you should change it to something like:
compilePostAsync(post).then(function (compiledPost) {
element.innerHTML = compiledPost;
});
Some notes:
This is only an example, it surely misses some things (proper error handling for example)
The isStaff and checkForStaffMemebership (original and new) do not get any argument, I guess you'd figure out how to pass the userId or any other data you might need
Read about promises, it's a useful tool to have, there is a lot of data about it on the web, for example: MDN.
As per the documentation you dont need to wrap the ajax with a promise which already implements promise. Instead chain the response as explained below.
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the Promise interface, giving them all the properties, methods, and behavior of a Promise (see Deferred object for more information)
You can do something like below by chaining the response:
function isStaff(url, post) {
return $.ajax({
url: url,
dataType:"json"
}).then(function(resp){
//resp = $.parseJSON(resp); /*You dont require this if you have respose as JSON object. Just Specify it in 'dataType'*/
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: resp.is_staff === 1 ? true : false
};
return template(context);
});
}
isStaff(url, post).done(function(template){
/*Your compiled template code is available here*/
}).fail(function(jqXHR, textStatus, errorThrown){
console.log("Error:"+textStatus);
});
Note: Be sure to implement error callbacks also. Because you may never know what
went wrong :)
Simple explanation about promise with $.defer:
For understanding i have created the Fiddle similar to your requirement.
Explanation:
Basically Promise is been introduced to attain synchronous execution of asynchronous JS code.
What do you mean by Async or Asynchronous code?
The code that is executed may return a value at any given point of time which is not immediate. Famous example to support this statement would be jquery ajax.
Why is it required?
Promise implementations helps a developer to implement a synchronous code block which depends on asynchronous code block for response,. like in ajax call when i make a request to server asking for a data string, i need to wait till the server responds back to me with a response data string which my synchronous code uses it to manipulate it , do some logic and update the UI.
Follow this link where the author has explained with detailed examples.
PS: Jquery $.defer implements or wraps promise in quite a different way. Both are used for the same purpose.
let basedataset = {}
let ajaxbase = {};
//setting api Urls
apiinterface();
function apiinterface() {
ajaxbase.createuser = '/api/createuser'
}
//setting up payload for post method
basedataset.email = profile.getEmail()
basedataset.username = profile.getGivenName()
//setting up url for api
ajaxbase.url = ajaxbase.createuser
ajaxbase.payload = basedataset;
//reusable promise based approach
basepostmethod(ajaxbase).then(function(data) {
console.log('common data', data);
}).catch(function(reason) {
console.log('reason for rejection', reason)
});
//modular ajax (Post/GET) snippets
function basepostmethod(ajaxbase) {
return new Promise(function(resolve, reject) {
$.ajax({
url: ajaxbase.url,
method: 'post',
dataType: 'json',
data: ajaxbase.payload,
success: function(data) {
resolve(data);
},
error: function(xhr) {
reject(xhr)
}
});
});
}
A solution using async await in js would be like this:
async function getMyAjaxCall() {
const someVariableName = await ajaxCallFunction();
}
function getMyAjaxCall() {
return $.ajax({
type: 'POST',
url: `someURL`,
headers: {
'Accept':'application/json',
},
success: function(response) {
// in case you need something else done.
}
});
}
I am just getting into unit testing and currently exploring the Mocha, Chai, Sinon setup, testing this in a browser.
I have a javascript block that makes an ajax call to the server like so
PromptBase.prototype.fetchAndSetTemplateString = function() {
var deferred = $.Deferred();
$.ajax({
url: "/someurl",
type: "GET"
})
.done(function(response) {
if (response.status.toLowerCase() === "success") {
deferred.resolve(response.templateString);
}
});
return deferred.promise();
};
PromptBase.prototype.fetchPromptTemplate = function() {
var promise = this.fetchAndSetTemplateString();
$.when(promise).done(function(promptHtml) {
//set the templateString to promptHtml here
});
};
//Register a custom event on document to fire ajax request.
$(document).on("customevent", function(event) {
promptInstance.fetchPromptTemplate(promptParams.fetchTemplateOnEvent);
});
Now I have a test case that looks like this using fakeServer
describe("TemplateFetch", function() {
before(function() {
this.server = sinon.fakeServer.create(); // and other setup
});
after(function() {
this.server.restore(); // and other clean up
});
it("should fetch template string from server, when fetchTemplateEvent is fired", function() {
var expectedTemplateString = "templateStringFromServer";
var templateAjaxUrl = '/someurl';
//faking a server response
this.server.respondWith("GET", templateAjaxUrl,
[200, {"Content-Type": "application/json"},
'{"status": "success", "templateString": "'+ expectedTemplateString +'"}']);
// Now trigger even that fetches the template
$(document).trigger("customevent");
// This calls the ajax done function, which resolves the promise
this.server.respond();
//debugger shows that templateString is set here
expect(this.instance.templateString).to.eqaul("something");
});
});
However my test outputs the timeout error
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
So the fakeServer.respond() call ensures that the ajax->done function is called resulting in the promise being resolved, And when I debug I can see that the expect test should match the two strings. How can I fix the time out issue here? I have also tried adding a done callback but to no avail.
Any input is appreciated. Thanks for reading.
I have the following block of code:
(function() {
QUnit.config.testTimeout = 10000;
var stringformat = QUnit.stringformat;
module('Web API GET Result has expected shape');
asyncTest('HomeData should return an array of Sets with their info as well as cards with their info, but no sides',
function () {
$.ajax({
url: '/sample/url',
dataType: 'json',
success: function (result) {
ok(
!!result.item, 'Got something');
start();
},
error: function(result) {
ok(false, 'Failed with: ' + result.responseText);
start();
}
}
);
}
);
return function () { asyncTest(); };
}
I'm trying to run this for multiple URLs with different results, but I can't figure out how to run asyncTest twice with different parameters. I tried assigning the function (second parameter of asyncTest) to a variable and inserting it into asyncTest() however this doesn't work. How can I define multiple "asyncTest"s and run them?
$.deferred(), $.when() and $.pipe() are your friends if you use jQuery [apparent from the code].
Good tutorials on the above are here, here
Hi the below Javascript is called when I submit a form. It first splits a bunch of url's from a text area, it then:
1) Adds lines to a table for each url, and in the last column (the 'status' column) it says "Not Started".
2) Again it loops through each url, first off it makes an ajax call to check on the status (status.php) which will return a percentage from 0 - 100.
3) In the same loop it kicks off the actual process via ajax (process.php), when the process has completed (bearing in the mind the continuous status updates), it will then say "Completed" in the status column and exit the auto_refresh.
4) It should then go to the next 'each' and do the same for the next url.
function formSubmit(){
var lines = $('#urls').val().split('\n');
$.each(lines, function(key, value) {
$('#dlTable tr:last').after('<tr><td>'+value+'</td><td>Not Started</td></tr>');
});
$.each(lines, function(key, value) {
var auto_refresh = setInterval( function () {
$.ajax({
url: 'status.php',
success: function(data) {
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>"+data+"</td>");
}
});
}, 1000);
$.ajax({
url: 'process.php?id='+value,
success: function(msg) {
clearInterval(auto_refresh);
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>completed rip</td>");
}
});
});
}
What you want is to run several asynchronous actions in sequence, right? I'd build an array of the functions to execute and run it through a sequence helper.
https://github.com/michiel/asynchelper-js/blob/master/lib/sequencer.js
var actions = [];
$.each(lines, function(key, value) {
actions.push(function(callback) {
$.ajax({
url: 'process.php?id='+val,
success: function(msg) {
clearInterval(auto_refresh);
//
// Perform your DOM operations here and be sure to call the
// callback!
//
callback();
}
});
});
}
);
As you can see, we build an array of scoped functions that take an arbitrary callback as an argument. A sequencer will run them in order for you.
Use the sequence helper from the github link and run,
var sequencer = new Sequencer(actions);
sequencer.start();
It is, btw, possible to define sequencer functions in a few lines of code. For example,
function sequencer(arr) {
(function() {
((arr.length != 0) && (arr.shift()(arguments.callee)));
})();
}
AJAX is asynchronous.
That's exactly what's supposed to happen.
Instead of using each, you should send the next AJAX request in the completion handler of the previous one.
You can also set AJAX to run synchronously using the "async" property. Add the following:
$.ajax({ "async": false, ... other options ... });
AJAX API reference here. Note that this will lock the browser until the request completes.
I prefer the approach in SLaks answer (sticking with asynchronous behavior). However, it does depend on your application. Exercise caution.
I would give the same answer as this jquery json populate table
This code will give you a little idea how to use callback with loops and ajax. But I have not tested it and there will be bugs. I derived the following from my old code:-
var processCnt; //Global variable - check if this is needed
function formSubmit(){
var lines = $('#urls').val().split('\n');
$.each(lines, function(key, value) {
$('#dlTable tr:last').after('<tr><td>'+value+'</td><td>Not Started</td></tr>');
});
completeProcessing(lines ,function(success)
{
$.ajax({
url: 'process.php?id='+value,
success: function(msg) {
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>completed rip</td>");
}
});
});
}
//Following two functions added by me
function completeProcessing(lines,callback)
{
processCnt= 0;
processingTimer = setInterval(function() { singleProcessing(lines[processCnt],function(completeProcessingSuccess){ if(completeProcessingSuccess){ clearInterval(processingTimer); callback(true); }})}, 1000);
}
function singleProcessing(line,callback)
{
key=processCnt;
val = line;
if(processCnt < totalFiles)
{ //Files to be processed
$.ajax({
url: 'status.php',
success: function(data) {
$('#dlTable').find("tr").eq(key+1).children().last().replaceWith("<td>"+data+"</td>");
processCnt++;
callback(false);
}
});
}
else
{
callback(true);
}
}
I am looking into QUnit for JavaScript unit testing. I am in a strange situation where I am checking against the value returned from the Ajax call.
For the following test I am purposely trying to fail it.
// test to check if the persons are returned!
test("getPersons", function() {
getPersons(function(response) {
// persons = $.evalJSON(response.d);
equals("boo", "Foo", "The name is valid");
});
});
But it ends up passing all the time. Here is the getPersons method that make the Ajax call.
function getPersons(callback) {
var persons = null;
$.ajax({
type: "POST",
dataType: "json",
data: {},
contentType: "application/json",
url: "AjaxService.asmx/GetPersons",
success: function(response) {
callback(response);
}
});
}
Starting and stopping using the QUnit library seems to be working!
// test to check if the persons are returned!
test("getPersons", function() {
stop();
getPersons(function(response) {
persons = $.evalJSON(response.d);
equals(persons[0].FirstName, "Mohammad");
start();
});
});
The real problem here isn't needing to call the start() and stop() methods - in fact you could get into trouble using that approach if you are not careful in calling stop() again at the end of your callback if you have additional .ajax() methods. Which then means you find yourself in some snarled mess of having to keep track if all the callbacks have been fired to know if you still need to call stop() again.
The root of the problem involves the default behavior of asynchronous requests - and the simple solution is to make the .ajax() request happen synchronously by setting the async property to false:
test("synchronous test", function() {
$.ajax({
url:'Sample.asmx/Service',
async:false,
success: function(d,s,x) { ok(true, "Called synchronously"); }
});
});
Even still, the best approach is to allow the asynchronous behavior and use the right test method invocation: asyncTest(). According to the docs "Asynchronous tests are queued and run one after the other. Equivalent to calling a normal test() and immediately calling stop()."
asyncTest("a test", function() {
$.ajax({
url: 'Sample.asmx/Service',
success: function(d,s,x) {
ok(true, "asynchronous PASS!");
start();
}
});
});
There a lot of qunit test in my project. like:
module("comment");
asyncTest("comment1", function() {
expect(6);
$.ajax({
url: 'http://xxx.com/comment',
dataType: "json",
type: "GET",
timeout: 1000
}).done(function(data) {
ok(true, "loaded");
ok(data.data.length>1, "array size");
ok(data.total, "attr total");
var c = data.data[0];
ok(c.id, "attr c.id");
ok(c.user_id, "attr c.user_id");
ok(c.type == 4, "attr c.type equal 4");
}).fail(function(x, text, thrown) {
ok(false, "ajax failed: " + text);
}).always(function(){
start();
});
});
ive done some qunit testing with ajax. its not pretty. the best thing i could come with is stopping the test when ajax is fired, and starting it again in the success callback. (using start() and stop()) methods. This meant one ajax request at a time, but i could live with that. Good luck