Jasmine Testing Fancybox in Backbone – Spied Methods incorrectly passing through-Collection of common programming errors
I have a modal login view that I’m testing with Jasmine. I have a nasty problem testing that the login button redirects. I’ve put the redirect in a method so that I can mock it and test the call but when I trigger the click event on the sign in button the spy seems to be ignored.
Here’s the view I’m testing…
define(['ministry', 'models/m-auth', 'helpers/cookie-manager', 'text!templates/modals/login.html', 'jquery.custom'],
function (Ministry, Authentication, CookieManager, TemplateSource) {
var loginView = Ministry.SimpleView.extend({
name: 'Login',
el: "#popupLoginWrapper",
template: Handlebars.compile(TemplateSource),
events: {
'submit #loginForm': 'signIn'
},
redirectToOrigin: function () {
// TODO: Change location to member home when implemented
location.href = '/#/jqm';
},
signIn: function (e) {
var that = this;
e.preventDefault();
// As we have no input yet log in manually using the auth model...
var authData = new Authentication.Request({
requestSource: $('#requestSource').text(),
apiKey: $('#apiKey').text(),
username: '*****',
password: '*****'
});
// This saves the login details, generating a session if necessary, then creates a cookie that lasts for 1 hour
authData.save(authData.attributes, {
success: function () {
if (authData.generatedHash !== undefined && authData.generatedHash !== null && authData.generatedHash !== '')
CookieManager.CreateSession(authData.generatedHash);
that.redirectToOrigin();
}
});
},
});
return loginView;
});
(It currently logs in with a hard coded account – wiring up the actual controls is the next job). And here’s the test in question (I’ve combined all the before and afters into the test for simplicity here)…
var buildAndRenderView = function (viewObject) {
viewObject.render();
$('#jasmineSpecTestArea').append(viewObject.el);
return viewObject;
};
it("signs in when the 'Sign In' button is clicked", function () {
spyOn(objUt, 'redirectToOrigin').andCallFake(Helper.DoNothing);
spyOn(Backbone.Model.prototype, 'save').andCallFake(function (attributes, params) {
params.success();
});
$('body').append('');
$('#jasmineSpecTestArea').append('');
objUt = new View();
buildAndRenderView(objUt);
objUt.$('#loginForm button').click();
expect(objUt.redirectToOrigin).toHaveBeenCalled();
$('#jasmineSpecTestArea').remove();
});
Some brief justification: I do the vast majority of my views as independent components that I then render in a custom view variant called a Region (modeled on Marionette) – I generally find this to be a nice tidy practice that helps me keep track of what sits where in the app separately from what is held in a given ‘space’. This strategy doesn’t seem to work with fancybox modals, such as this, so the render takes place in a hidden existing DOM element and is then moved into the region, hence the need for the code to append ‘popupLoginWrapper’ into the test area.
The same test above will also fail if I call the sign in method directly with empty event arguments. Jasmine gives me the following error ‘Error: Expected a spy, but got Function.’ (Which makes sense, as it’s calling the actual implementation of redirectToOrigin rather than the spy.
I have similar tests which pass, and the issue seems to be around the triggering of params.success(), but this is necessary to the test.
ADDITIONAL: After disabling the test, I found this issue affecting most of the tests for this modal. By stepping through, I can see that the spy is applied and the spy code is executed in some cases, but then the real success call seems to then be triggered afterwards.