views:

371

answers:

4

Hi all:

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.

A: 

You can use FireBug to set break points, watch variables, fiddle with the DOM and more:

http://getfirebug.com/js.html

pygorex1
+2  A: 

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:

  1. Your interaction with the framework. This includes things like making sure that you call the right functions with the right parameters.
  2. 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.")
});
Igor Zevaka
@Igor: for your example, what if there is a bunch of logic that does not call external functions (like myInit)? I tried to write unit test to test myFunction, but I just couldn't get into the document.ready (syntax wise). There are a lot of $(document).ready(function() {}); in my code, hence I felt the need to test them. And then I was told unconvincingly that I don't need to test document.ready. Hence I got confused. Thanks.
BeraCim
A: 

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:

  1. One test to verify the right function is set as the the ready handler
  2. 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() {
}
Frank Schwieterman
So there is no way to test the anonymous code block apart from taking the code out to a separate function?
BeraCim
You could use JSMock's andStub() functionality to record the passed in callback, then call that callback from the same test. This would not be desirable though, as now you have a test verifying separate things (leading to poor defect localization http://xunitpatterns.com/Goals%20of%20Test%20Automation.html#Defect%20Localization)
Frank Schwieterman
A: 

For answer to this question, see posts here and here.

BeraCim