This question already has answers here:
How to invoke $(document).ready(function() {}) in unit testing
(5 answers)
Calling $(document).ready(function() {...}); from another file
(1 answer)
Closed 5 months ago.
I have a question in regards to unit testing jQuery's document.ready function().
Currently I have 2 scenarios in my code:
function myFunction()
{
$(document).ready(function() { ... });
}
And:
$(document).ready(function()
{
// some really long setup code here
});
I tried to write a unit test for the first scenario, but I just couldn't get it to run into the document.ready function. As for the second scenario, I haven't come up with a way to test it yet (I'm having trouble coming up with both a way to test it and the syntax).
So assuming I cannot change the source code, are there any ways to test those functions? (assuming it is a good idea to test them)
Thanks.
You do not need to test $(document).ready as it is part of the framework and is already unit tested. When writing unit tests you need to test two things:
Your interaction with the framework. This includes things like making sure that you call the right functions with the right parameters.
Your own code - that your code does the right thing.
So what you really need to do is to make sure that whatever code that gets called from $(document).ready is correct.
function myInit(){
//...
}
function myFunction()
{
$(document).ready(myInit);
}
All you need to do now is to unit test myInit function.
What you can also do is mock out $.ready function to make sure that you are calling it:
var readyCalled = false;
$.ready = function(func){
readyCalled = (myInit == func);
}
//Your code containing `myInit` will get executed somewhere here
//....
//Then test:
test("Should have called ready", function() {
ok(readyCalled, "ready should have been called with myInit as a parameter.")
});
The function that registers the on ready handler should register another function, not an anonymous codeblock. Then you can test the code that calls $.ready() separate from the code that runs on ready. So you have:
One test to verify the right function is set as the the ready handler
Another test to verify the ready handler does the right stuff
To test scenario 1, you'll need to inject a test double for jQuery. This is difficult as if you redefine $ or jQuery, odds are you'll screw up other code that relies on it for other processing (like the test runner). At the same time your code may still want to call jQuery directly when its using utility methods like array concatenation. Any inversion-of-control pattern should address this though (http://martinfowler.com/articles/injection.html).
Anyhow, here's some code using constructor injection (using JSMock for the mocking library, and QUnit (of jQuery) for the test runner):
// the code
var createComponent = function(_$) {
var that = {};
that.OnStart = function() {
_$.ready(this.OnReady);
};
that.OnReady = function() {
};
return that;
};
// the test
test("OnStart associates the ready handler", function() {
var sut;
var mock$ = mc.createMock($);
mock$.expects().ready(isA.TypeOf(Function)).andStub(function(callback) {
equals(callback, sut.OnReady);
});
sut = createComponent(mock$);
sut.OnStart();
mc.verify();
});
test("OnReady does the right stuff", function() {
//etc
});
I use this general pattern for all event handlers in JS... You might prefer to use prototype type classes. When you pass functions as parameters to jQuery, you need to be aware that the "this" value will not be set by jQuery when those callbacks are called. In the test, this breaks because equals(callback, sut.OnReady) no longer passes. To address this, you need to make the event handlers direct members of each instance. You can imagine when there are a number of then its nice to have a util that takes a list of them, but this demonstrates making 'OnReady' a member who does not rely on 'this'.
var Component = function(_$) {
this._$ = _$;
// repeat for each event handler thats tested
this.OnReady = function() {
Component.prototype.OnReady.apply(this);
}
}
Component.prototype.Start = function() {
this._$.ready(this.OnReady);
}
Component.prototype.OnReady = function() {
}
Related
I'm new to JavaScript. Imagine, I have following piece of code:
$(document).ready(function() {
$('#nights').on('keyup', function() {
var nights = +$(this).val();
var dailyPrice = +$(this).closest(".tour").data("daily-price");
$('#total').text(nights * dailyPrice);
$('#nights-count').text($(this).val());
});
});
How can I unit-test that the anonymous function
retrieves the value of the current element,
retrieves the data from +$(this).closest(".tour").data("daily-price") and
then calls text(...) on $('#total') and $('#nights-count')
?
Note that I'm interested in unit tests (therefore creating a full-fledged a Selenium test suite, which types in something and then checks the value of the elements isn't suitable for me), which don't require me to add a new abstraction layer.
By abstraction layer I mean this: I could create class JQueryAbstraction and then 2 sub-classes - one, which calls real jQuery methods and another, which just counts the number of calls.
You can try introducing the popular Unit Testing lightweight frameworks like Jasmine, Mocha, QUnit
All these framework can co-exist with AngularJS, JQuery....etc
From your example, which you are trying with an anonymous function, you can convert that function to a non-anonymous function and pass it to the testing tool
to make sure the last line is reached I am introducing a simple global variable and assigning value to it, say done = true or done = false
Example:
var done = false;
var onReady = function() {
$('#nights').on('keyup', function() {
var nights = +$(this).val();
var dailyPrice = +$(this).closest(".tour").data("daily-price");
$('#total').text(nights * dailyPrice);
$('#nights-count').text($(this).val());
});
done = true;
}
$(document).ready(onReady);
So now you can test your onReady code with unit testing as in Jasmine, you can comment the actual document ready
//$(document).ready(onReady);
And call the function via unit testing
describe("Document Ready Unit Testing", function() {
it("call the function ", function() {
done = false;
onReady() ;
expect(done).toEqual(true);//this will print the unit testing passed or failed
});
});
But: If you are not interested in declaring the anonymous function separately then you can loop through the document object and get the ready event function.
I'm trying to test a JavaScript function nested within a jQuery function using Jasmine in a Rails application as follows:
jQuery (function () {
var javascriptFunction = function () {
...functionality being tested...
}
}
When I run the Jasmine test, I get the following error: "ReferenceError: Can't find variable: javascriptFunction"
If I place the JavaScript function outside of the jQuery function, I no longer receive that error; however, that JavaScript function needs to remain within the jQuery function in order for the rest of the application to work.
Is there a way to specify to Jasmine to look for the JavaScript function within the jQuery function?
Ok so now I understand what you are trying to achieve.
There is no way to test what you have done as it is anonymous as you describe in your comments. The best way here is to add that code as a function somewhere. Could be on the window, or preferably on some other class somewhere. You can then test the outcome of calling your function at any point.
The JQuery ready function you are using is really just a way of starting your application. It says 'ok things are ready lets go'. In testing you are wanting that to happen and be repeatable so you can't rely on JQuery to do this.
//Current working version
function doTheThing(){
if ($("#isac").length && $("#isac")[0].checked) {
$(".nested-form-container").show();
enableGraduatedCheckbox();
} else {
$(".nested-form-container").hide();
}
}
jQuery (function () {
doTheThing();
}
Here is a similar discussion around the testability of what you are trying to do and the same suggestions. https://softwareengineering.stackexchange.com/questions/204389/how-to-test-functions-or-the-code-inside-document-ready-using-jasmine
I have a script with the following structure:
Test = {
CONSTANTS : {},
VARIABLES : {},
MARKUP : {},
FUNCTIONS : {
init : function () {
// Access variable from different namespace
var all_constants = DifferentNamespace.CONSTANTS; // WORKS
var tester = DifferentNamespace.CONSTANTS.chunk_of_markup; // SAYS UNDEFINED
}
},
init : function () {
// Call real init() function
$(document).ready(function () {
Test.FUNCTIONS.init();
});
}
};
$(document).ready(function () {
Test.init();
});
If I remove either of the $(document).ready(..) function calls, when I try to access a constant from a different namespace it is undefined; with both is works well.
As you can see I'm using two init() functions, one it just to neaten up the call to init because I have wrapped functions inside an additional object.
If I remove the function that is on the same level as CONSTANTS, VARIABLES etc and try to call the init() within Test.FUNCTIONS it still does not work.
Edit:
If i console.log(all_constants) I get the full object (with .chunk_of_markup) but if I console.log(tester) is get undefined. If i wrap tester i get []
I should also note that the other namespace gets the markup from a seperate file.
Any ideas why?
Having two document ready doesn't make a difference here. You could have one document.ready and/or call Test.FUNCTIONS.init directly and all should work, and the fact that they are in different namespaces doesn't matter as well.
As for why you're getting undefined, I think it is probably because your chunk_of_markup variable is actually undefined at that point. My guess is that you're getting the value for it through AJAX and so the call is done asynchronously which means the DOM will be ready before it actually returns a value. When you use the Debugger then the value is evaluated at the point of time where you run the command so by then, the async call already returns successfully (it's a race condition, if you're fast enough and your AJAX is slow then you can still get undefined, and it's also why 2 ready functions happen to make it slow enough for the AJAX call to return but it's still unreliable).
In all cases, if my theory is correct, then you need to hook to the callback of the AJAX request rather that DOM ready event, this is the only place where you can guarantee that your variable is defined.
Why not call the function init() in the document Handler itself.. I don't think that will lead to the same problems.. You can remove the Test.init() completely as it does not seem to do anything in here
Test = {
CONSTANTS : {},
VARIABLES : {},
MARKUP : {},
FUNCTIONS : {
init : function () {
// Access variable from different namespace
var all_constants = DifferentNamespace.CONSTANTS; // WORKS
var tester = DifferentNamespace.CONSTANTS.chunk_of_markup; // SAYS UNDEFINED
}
}
};
$(document).ready(function () {
Test.FUNCTIONS.init();
});
I am working with a user control that has set of javascript functions that are called when an action is performed. This user control is used in a lot of places in the application.
When one of the inbuilt JS function completes execution, I need to fire a custom JS function on my page.
Is there a way for me to attach a function to be fired when another function completes execution? I don't want to update the inbuilt JS function to call this page JS function.
Hope this makes sense.
There are a couple design patterns you could use for this depending upon the specific code (which you have not shared) and what you can and cannot change:
Option 1: Add a callback to some existing code:
function mainFunction(callbackWhenDone) {
// do other stuff here
callbackWhenDone();
}
So, you can call this with:
mainFunction(myFunction);
Option 2: Wrap previous function:
obj.oldMethod = obj.mainFunction;
obj.mainFunction = function() {
this.oldMethod.apply(this, arguments);
// call your stuff here after executing the old method
myFunction();
}
So, now anytime someone does:
obj.mainFunction();
it will call the original method and then call your function.
You're basically trying to do callbacks. Since you're not mentioning what functions you're talking about (as in code), the best thing to do would be basically to wrap the function, -quick and dirty- and make it work with callbacks.
That way you can pass it a Lambda (Anonymous Function) and execute anything you want when it's done.
Updated to demonstrate how to add Callbacks:
function my_function($a, $callback) {
alert($a);
$callback();
}
my_function('argument', function() {
alert('Completed');
});
The ugliest and best solution is to monkey-patch the built-in function. Assume the built-in function is called "thirdParty":
// first, store a ref to the original
var copyOfThirdParty = thirdParty;
// then, redefine it
var thirdParty = function() {
// call the original first (passing any necessary args on through)
copyOfThirdParty.apply(this, arguments);
// then do whatever you want when it's done;
// custom code goes here
customFunction();
};
We've essentially created a modified version of the built-in function without ever touching the original version.
Since Javascript is highly dynamic you can modify the original function without modifying its source code:
function connect_after(before, after){
return function(){
before.apply(this, arguments);
after();
};
}
var original_function = function(){ console.log(1); }
original_function = connect_after(original_function, function(){ console.log(2); })
I am relatively new to javascript so please be patient if what i am asking is completely stupid!
I am trying to make a simple module. Inside the module i want to have a config object that holds settings for the module. I am also using jquery. The jquery selectors work only when in a function directly in the main object/module.
I understand that javascript has functional scope so I am suprised that I cannot use the jquery selectors anywhere inside the module.
EDIT:
I want to be able to directly set all of my configs inside the configs object using jquery selectors. This way i keep all the messy stuff inside one place and can then access configs.whatever throughout the rest of the module. At the moment jquery selectors do not work inside the configs module.
var OB = function() {
var configs = {
'mode' : 'test',
'numOfSelects' : $('.mySelect').find('select').length, // This doesnt work
}
var getMode = function() {
return configs.mode;
}
function init() {
alert(configs.numOfSelects); // This alerts 0 until the following line
alert($('.mySelect').find('select').length); // This correctly alerts 2
};
var handlers = {
successHandler : function() {
alert("Success");
},
errorHandler : function() {
alert("error");
}
}
return {
init : init,
getMode : getMode
}
}( );
$(document).ready(function(){
OB.init();
});
It isn't that jQuery isn't in scope — that's that the code isn't executing when you think it is. The variable config is defined when that anonymous function (var OB = function() {}()) is executed. The DOM isn't ready yet, so that DOM traversal doesn't find anything. When you do the DOM traversal in init(), that isn't executed until it's explicitly called inside the $(document).ready() handler, at which point that DOM is set up. That's the difference you're seeing.
OB() needs to be called after the DOM has completely loaded. Hence the answer by Marcelo, which calls OB() in the ready() method.
EDIT: It's funny that my original answer below was incorrect because I didn't notice two little parentheses at the end of the definition of OB, and it turns out that these are the culprit. You define and then immediately invoke OB, which is before the DOM has been fully loaded. Remove those parentheses and make the change I suggest below.
Calling OB() returns an object with init and getMode, but you haven't called OB(), you've only referred to OB. Try this instead:
$(document).ready(function(){
OB().init();
});
Also, I assume you want to later refer to getMode. In particular, you will to get the copy of getMode that has access to the same local scope that your init() call had access to. To achieve this, you will need to store the result of calling OB() for later use:
var ob;
$(document).ready(function(){
ob = OB();
ob.init();
});
function some_other_function() {
... ob.getMode() ...;
}