Testing Backbone.js Model save using Sinon not calling success callback - javascript

I'm testing a Backbone.js app using Jasmine and Sinon. I'm trying to verify that clicking a button click calls the Model's save() method and processes the success callback which adds a message to the view's el element. I'm having trouble getting the sinon server to trigger the Model's success callback.
Heres what my spec's beforeEach looks like (the variables in beforeEach are all is var scoped in the describe function).
beforeEach(function(){
server = sinon.fakeServer.create(); //create the fake server
server.respondWith([200, { "Content-Type": "text/html", "Content-Length": 2 }, "OK"]); //fake a 200 response
loadFixtures('signup_modal.html'); //load the fixture
element = $("#signupModal");
specSignUp = new SignUp();
signUpView = new SignUpView({model : specSignUp, el: $("#signupModal")});
});
And this is what the actual test looks like:
it("Should call send request",function(){
element.find("#signupButton").trigger('click'); //click the button which should trigger save
server.respond(); //fake the response which should trigger the callback
expect(element).toContain("#message");
});
While trying to build the implementation of this I created a simple callback method to show me that the success callback is beign triggered:
sendRequest: function(){
console.log("saving");
this.model.save(this.model.toJSON(),{success: function(data){
console.log("success");
iris.addMessage(this.$("#messageContainer"),"Thank you");
}});
}
When I run the test the console shows "saving" but the success callback isn't getting called.

Backbone expects the response text to be valid JSON and was bombing out because of the response "OK" in the server.respondWith() method.
Changing the method to:
server.respondWith([200, {"Content-Type":"text/html","Content-Length":2}, '{"OK":"True"}']);
The success callback was being processed successfully.

Related

In a Jasmine unit test, how do I force a failure callback to trigger that would result from a failed request?

Let's say I have this method (bellow is the function extrapolated):
function doSomething(onSuccess, onFailure){
var that = this;
that.$save(function(result){
if (onSuccess) {
onSuccess(result);
}
}, onFailure);
}
I'm already successfully testing that the onSuccess callback fires when that.$save executes as expected, but I'm having trouble finding any documentation on how to make that.$save fail to trigger the onFailure callback.
My attempt at creating a onFailure test looks like:
it ('should trigger the failure callback if necessary', function(){
var successCallback= jasmine.createSpy('successCallback');
var failureCallback= jasmine.createSpy('failureCallback');
// Force $save to fail to trigger failure callback
thing.$save = function(){};
spyOn(thing, '$save').and.throwError('thing.$save error');
thing.addNew(successCallback, failureCallback);
expect(failureCallback).toHaveBeenCalled();
expect(callback.successCallback).not.toHaveBeenCalled();
});
It still tries to call the success callback as I can tell per this error:
Expected spy failureCallback to have been called.
at /Users/pathy-path
Error: Unexpected request: POST http://website.com/things/5654d74a9b8d05030083239f
No more request expected
And if I setup the httpBackend to expect the POST (which it shouldn't since it should fail? At least that seems logical), the test fails because the wrong callback executes: Expected spy failureCallback to have been called.
For reference, my onSuccess test looks like this (somewhat simplified):
it ('should trigger the success callback', function(){
var successCallback = jasmine.createSpy('successCallback');
path = ENV.apiEndpoint + '/things/' + id;
// if this is triggered we know it would be saved
$httpBackend.expect('POST', path).respond(201);
thing.addNew(successCallback);
$httpBackend.flush();
expect(successCallback).toHaveBeenCalled();
});
You need to have it execute the second (onFailure) callback
spyOn(that, '$save').and.callFake(function(success, fail) {
fail();
});
I tried Phil's answer (which makes the test pass) but felt that it may not actually be a true test of how the doSomething function executes. To mimic an API failure I ended up using $httpBackend and forcing a 404 response so that Angular's .$save method still executes:
it ('should trigger the failure callback if necessary', function(){
var successCallback= jasmine.createSpy('successCallback');
var failureCallback= jasmine.createSpy('failureCallback');
// Force $save to fail to trigger failure callback
path = ENV.apiEndpoint + '/things/' + thing.id;
$httpBackend.expect('POST', path).respond(404);
thing.addNew(successCallback, failureCallback);
$httpBackend.flush();
expect(failureCallback).toHaveBeenCalled();
expect(successCallback).not.toHaveBeenCalled();
});

Form.Request event does not fire

I'm having trouble with events in a MooTools Form.Request():
new Form.Request(e.getParent('form'), $('update'), {
resetForm: false,
onRequest: function () {
loading(e)
},
onSuccess: function () {
toggle(e)
}
}).send();
The onSuccess event triggers happy, but the onRequest event does nothing. Am I overlooking something?
The name of the event is send not request. So you are looking for onSend:.
Have a closer look at the documentation:
Events:
send - (function) The function to execute when the request is sent. Passed the form being submitted and the data (an object) being submitted.
failure - (function) The function to execute when the request fails. Passed the XHR that is returned by Request on failure.
success - (function) The function to execute when the request succeeds. Passed the target being updated, the request text, and the request xml.

Global ajaxSuccess event before local success event

I have an ASP MVC application that lets a user add multiple partial views to a div with jQuery ajax calls.
jQuery("#AddNew").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) {
jQuery("#DivId").append(html);
}
});
return false;
});
The problem is that since a user must be authorized for the action returning the partial view, when a user's session has timed out, it is rendering the login page instead of the partial view. I have multiple places that are using similar ajax calls, so I added the following as a global ajaxSuccess event:
jQuery(document).ajaxSuccess(function (event, request, settings) {
var isLogin = jQuery(request.responseText).find('.login-form').length;
if (isLogin > 0) {
var url = document.URL;
var rootPath = '#Request.Url.GetLeftPart(UriPartial.Authority)';
var path = url.replace(rootPath, '');
var pathEncoded = encodeURIComponent(path);
var loginURL = rootPath + "/Account/Login?returnUrl=" + pathEncoded;
location.href = loginURL;
}
});
This works, as it will redirect the user to the login page when an unauthorized ajax request is made. However, it is still adding the html to the div, which is visible for a short time before the redirect.
Is there a way to get the global event to trigger before the local one? The jQuery API shows that the local success event is triggered before the global ajaxSuccess event, so I tried changing the ajax call to use complete, rather than success. This does work, but it seems like if for some reasons I needed to add code in the future that only executes on success, that I'll run into the same problem. Is there a better way to handle this?
I might advise creating your own API wrapping the ajax method which ensures the functionality you desire (in particular, the order of operations). Here's a very over-simplified example:
var async = function(props) {
var finished = $.Deferred();
$.ajax(props)
.done(function(response) {
// detect auth timeout, handle consisently
if(response.isAuthTimeout) {
// render login ui
finished.reject('auth-timeout');
} else {
finished.resolve.apply(finished, arguments);
}
})
.fail(function() {
finished.reject.apply(finished, arguments);
})
return finished;
};
Then, in practice you'll make calls to async (or whatever you decide to call your wrapper) rather than the native $.ajax API.
Make sense?

Edit Backbone Ajax Success Method to Add in Custom Error Handling

I have my own custom error code inside of a backbone ajax success method in case the server returns an error. The problem is that this code is repeated throughout my app and I wanted to edit the success function in one place so I don't have to constantly repeat this error handler in every ajax success. I want to edit the success function to include this error check wrapper. Do you know how to do that?
Here is an example of my success method in one of my views:
"success" : function success(model, data)
{
if(data['error'] !== undefined && data['error'].length === 0)
{
message('error', 'Whoops! System Error. Please refresh your page.');
}
else if(data['error'] !== undefined)
{
message('error', data['error']);
}
else
{
//add templates and do stuff here
}
},
Ideally I'd like to set that in a config somewhere and then I'd just be able to use:
"success" : function success(model, data)
{
// add templates and do stuff here
}
Is this possible? I tried using ajaxSetup but that didn't seem to work for me.
UPDATED CODE STILL NOT WORKING:
That does get me a little further along but the error handler isn't functioning as a wrapper. The data is not being passed into my ajax calls. In fact, my success methods on my ajax calls aren't running at all anymore. I tried console.log("some text") in my ajax calls but nothing is being output. Do you know what is wrong with this?
// Save the original Sync method
defaultSync = Backbone.sync;
//Over ride Backbone async
Backbone.sync = function(method,
model,
options)
{
success = options.success
options.success = function(data)
{
if(data['error'] !== undefined && data['error'].length === 0)
{
message('error', 'Whoops! System Error. Please refresh your page.');
}
else if(data['error'] !== undefined)
{
message('error', data['error']);
}
else
{
success(model,
data);
}
}
return defaultSync(method,
model,
options)
}
There are two ways to solve this:
Inheriting Backbone Model
You could create your own custom model which inherits from Backbone Model. In it you could override the save method. Read Backbone docs on how to extend their model
In your custom save method, you will call the save method of super, check the responseText, if it's success then you'll call the success callback. (please do read backbone docs on how to call a method of your parent model in Javascript)
Override Backbone.Sync
Backbone has a Sync module which basically by default makes all ajax requests, parses the response and then calls the success/error callbacks you specified when calling save on your model. It's pretty simple. Take a look at this doc . Again you could override this, do exactly what Backbone is doing by default but only call the success/error callbacks based on responseText you received.
UPDATE: Sample Code (warning code not tested)
//Over ride Backbone async
defaultSync = Backbone.Sync // Save the original Sync method. We'll be needing that.
Backbone.Sync = function(method, model, options) {
success = options.success
error = options.error
options.success = function(model, data, options) {
if (/% all your custom checks are true */) {
success (model, data, options);
}
else {
error(model,data,options);
}
}
return defaultSync(method, model, options);
}
Please make sure with this strategy the Sync method will be overriden for ALL your Backbone sync. If you don't want that then use Model#save override.
Take a look at this code where I am overriding Backbone.Sync to make it work with Parse.com API.

Call a function everytime and AJAX request is processed and returned from the server

I have a application where there are numerous number of ajax calls to the server.
Now I want to audit the response that comes from the server (This requirement poped up after the ajax code was laid).
So I have a function that would audit the response data, only problem is how can I get the data to be sent to the function which now sits separately.
I don't want to do the laborious work of adding the line of code for calling the function in each ajax call.
Is there easier and general way out. Somehow I could detect when a response come back and then process the response.
Using both traditional javascript method as well as jquery ajax calls in the system. (The app has been getting changes from a long time and has changed hands a lot so the new things get added and the older ones never get removed)
Wrap your ajax calls with a helper function and use it throughout your code.
An (untested) example:
MyApp = MyApp || {
logRequest: function _logRequest(settings, response) {
// Log your response
},
ajax: function _ajax (settings) {
var that = this;
// Log attempt request here?
// Example of logging the success callback (do similar for error or complete)
if (settings.success) {
// A success handler is already specified
settings.success = function (data) {
that.logRequest(settings, data); // Log the response
settings.success(data); // Call the original complete handler
};
} else {
// No success handler is specified
settings.success = function (data) {
that.logRequest(settings, data);
};
}
return jQuery.ajax(settings);
}
};
I favour this mechanism for lots situations where I want to reduce boilerplate. I only have to modify the state of the MyApp object which is my own (named appropriately for the application), so it is sort of an interface that allows you to intercept function calls without modifying other global objects. You can also swap this functionality out with something else very easily without having to update your references everywhere, which could be useful in a lot of other situations as well.
Using .ajaxComplete() should be enough to catch the onComplete event for all AJAX requests made through jQuery. Isn´t that what you´re asking for?
$('.ajaxRequest').click(function(event) {
event.preventDefault();
$.getJSON(
'/echo/json/',
this.id,
function(data, textStatus, jqXHR) {
console.log(data, textStatus, jqXHR);
}
);
});
// Listen to all ajax requests
$("#log").ajaxComplete(function(event, request, settings) {
console.log(event, request, settings);
});​
View demo.

Categories