Turn your jQuery code into a richer, unit testable, plugin

I find myself increasingly using jQuery as my JavaScript framework of choice.

It’s by-line of “write less, do more” really seems apt.

But sometimes, by writing just that little bit extra, you can do even more.

For example, I often try to do the following:

  • Make most jQuery code into reusable plugins
  • Use the jQuery plugin development pattern for added flexibility
  • Use QUnit to unit test JavaScript
  • Combine the two approaches to drive out a richer API for the plugin

By unit testing with QUnit, I find I often need to trigger additional events or add additional code from within the plugin so the test can be meaningful.

But this extra code isn’t only useful for testing, it becomes a useful part of the plugin’s API, improving its functionality and flexibility without sacrificing maintainability and readability of the code.

I’ll try to demonstrate that in this post.

Make most of your jQuery code into reusable plugins

In many cases, where there is a block of jQuery code initializing something, it is a candidate for a plugin.

It took me a little while before I gave jquery plugins a go, thinking it will be complex and I won’t have time. But, it turns out to be really simple, elegant and useful for both simple and complicated scenarios.

Example to create a dynamic side note toggler

Lets take a simple example for illustration: suppose I have some HTML that acts as an aside, or side note, and that I want to toggle its appearance.

Lets say we agree this kind of HTML format for it (or microformat):

<p>Some text before the side note.</p>

<div class="side-note">
	<p>Any HTML could go here including</p>
	<ul>
		<li>Bulleted lists</li>
		<li>Tabular data</li>
		<li>Images</li>
	</ul>

	<div class="side-note">
		<p>Even nested side notes, if that is of any use!</p>
	</div>
</div>

<p>Some text after the side note.</p>

The first way I’d do it might be something like this:

$(document).ready(function() {
    $('.side-note').each(function() {
        $(this)
            .addClass('dynamic-side-note')
            .hide()
            .wrap('<div class="dynamic-side-note-container"></div>')
            .before('<h3 class="toggler"><a href="#">Side note:</a></h3>')
            .parent(0).find('> h3.toggler > a').click(function() {
                $(this).parents('.dynamic-side-note-container').eq(0).find('> .dynamic-side-note').slideToggle();
                return false;
            });
    });
});

I could have added a click event handler to the h3 header above, but using an anchor adds keyboard accessibility.

(The CSS selector could also be improved, e.g. to narrow it down to only side-notes in some content div, e.g. $('#content .side-note').)

Here is a working example

As a simple jQuery plugin

The above works. But, we can move most of the above code into a jQuery plugin.

Why bother? We can gain a bit more flexibility, such as the ability to use any selector, not rely on a side-note class. We could pass in other parameters such as what the text for the side note toggler/header should be, etc.

As a first attempt, the plugin might look something like this:

$.fn.sideNotes = function() {
    // returning this way allows chaining
    return $(this)
        .addClass('dynamic-side-note')
        .hide()
        .wrap('<div class="dynamic-side-note-container"></div>')
        .before('<h3 class="toggler"><a href="#">Side note:</a></h3>')
        .parent(0).find('> h3.toggler > a').click(function() {
            $(this).parents('.dynamic-side-note-container').eq(0).find('> .dynamic-side-note').slideToggle();
            return false;
        });
};

We then just need to invoke the plugin:

jQuery(function() {
    $('.side-note').sideNotes();
});

(Note how you could use any selector now not just .side-note as above.)

Here is a working example using the simple plugin

Plugins also make it easier to pass in more options for configuration. The next example uses a useful pattern for plugin development that also shows a nice way to handle plugin options:

Use the jQuery plugin development pattern for added flexibility

The post, A Plugin Development Pattern (for jQuery), from the Learning jQuery blog is really useful.

It provides a good way to write plugins that also get the following features:

  • Configurability by passing in options
  • Default options to keep invoking code small and neat
  • A closure where you split your code out into manageable private functions, etc., if you need
  • Ensuring that your plugin support chaining
  • And more

Using some of those ideas, here is what we might come up with for the sideNotes plugin:

(function($) {
    $.fn.sideNotes = function(options) {

    // build main options before element iteration by extending the default ones
    var opts = $.extend({}, $.fn.sideNotes.defaults, options);

    // for each side note, do the magic.
    return $(this)
        .hide()
        .wrap('<div class="dynamic-side-note"></div>')
        .before('<h3 class="toggler"><a href="#">' + opts.sideNoteToggleText +'</a></h3>')
        .parent(0).find('> h3.toggler > a').click(function() {
            $(this).parents('.dynamic-side-note').eq(0).find('> .side-note').slideToggle();
            return false;
        });
    };

    // plugin defaults
    $.fn.sideNotes.defaults = {
        sideNoteToggleText : 'Side note:'
    };
})(jQuery);

And invoking the plugin is the same as before:

jQuery(function() {
    $('.side-note').sideNotes();

    // or overriding the default side note toggle text:
    // $('.side-note').sideNotes({ sideNoteToggleText : 'As an aside:' });
});

Here is a working example using the plugin pattern

Unit testing the jQuery plugin

As the plugin code is reasonably well encapsulated, we can create some unit tests for this plugin.

Unit testing jQuery (as with any code) gives you confidence in maintaining it. For example, when refactoring code, unit tests give you confidence that you can do it without breaking things. Plugins, almost by definition are good candidates for unit testing.

Examples of unit tests for this plugin

Our example side note plugin is quite simple, so the unit tests will likely be small in number. Types of tests we might include are the following:

  • Test that we can expand/collapse a side note
  • Test that we can expand/collapse a side note many times and ensure the toggle state reported is correct each time
  • Test that when the side note plugin has run, all the side notes are collapsed initially
  • Test that we can change the toggle text to something else

And so on.

Remember, the unit tests should not test the slideToggle() method we happened to use (as that should be unit tested itself, and we may use other ways of toggling the side note in the future). Instead, we just need to unit test our code and plugin functionality.

So ideally, we might even want to “mock” the slideToggle(), if necessary.

Use QUnit to unit test JavaScript

QUnit is a unit testing framework, used internally by jQuery itself for all its core JavaScript, and now opened up and documented for others to use, too. It is a simple framework to support the creation and execution of tests.

The tests are run in a browser. You could automate it against all your target browsers by using something like Selenium.

The framework is in its early days (a setup and teardown set of methods would be nice, for example), but is rich enough to get started with it.

Here is a QUnit test page for the side note plugin

(I included a manual test area as this can sometimes be useful where visual confirmation is useful or automated tests of some parts is not possible/easy. Having this all in one place can be handy.)

Using unit tests and plugins helps create a richer API for the plugin

To write testable code, you may find you need to provide more hooks in the code. Yet, you probably don’t want to pollute your code so much that it is detrimental to performance or maintainability.

Triggering events on the plugin for added flexibility

In the unit test example page, how did we manage to get unit tests to confirm a side note had been toggled when we didn’t have any callback for it?

The first idea people might have is to pass a callback that a plugin user can point to in the options when invoking the plugin, something like this:

jQuery(function() {
    $('.side-note').sideNotes(
        {
            toggled : function() { console.log('side note was toggled'); }
        }
    );
});

That looks useful; the unit test can provide a callback when it invokes the plugin.

However, that limits us to just one observer, the one that invoked the side note plugin in the first place.

But we can go one step further: trigger events. This allows more than one observer to watch for the event. This is achieved using jQuery’s trigger() and bind() methods.

By having unit tests watching for these events, it does not distort the plugin code; it enhances its API making the event useful for a real plugin user, if they need it.

In our side note plugin example, when we call slideToggle() we can trigger an event when the toggling has completed:

// all the stuff that gets to the
// slide toggle bit comes here!
.slideToggle( function() {
    $(this).trigger('sideNoteToggled');
});

If your code cares when this happens, you can bind to this event, something like this:

$('.side-note').bind('sideNoteToggled', eventHandlerGoesHere);

(A fuller example below uses trigger() to also pass the expanded/collapsed state in conjunction with some ARIA information.)

Providing default implementations that can be replaced (or mocked for unit testing)

One particular challenge I had with unit testing this side note example was that the slideToggle() method used by the plugin, internally runs asynchronously (using setTimeout etc).

Writing normal unit test code means the test can finish before the animation has run.

Even though I tried to use setTimeout() on the tests themselves to try and make them wait for the sideNote to finish toggling, and passing in the fastest speed possible to the slideToggle method, it wasn’t always right, and not consistent across all browsers.

This gave me the opportunity to do a few things:

  • Encapsulate the call to slideToggle in another method
  • Make that method the default implementation, but overrideable
  • Use this to provide a mock slide toggler for testing purposes

With jQuery’s plugin architecture providing a default implementation is quite straight forward:

$.fn.sideNotes.toggle = function() {
    $(this).slideToggle(function() {
        $(this).trigger('sideNoteToggled');
    });
};

Mocking the slideToggle() call is nice because we don’t want to test external code in our unit tests; that should have been unit tested by whoever wrote that (the jQuery team I imagine! I’ll have to look at their unit tests when I get a moment to see how they overcome asynchronous issue — there is some mechanism to provide stop() and start() methods for AJAX testing and I tried this here, but still wasn’t getting consistent results).

In our example, we can overwrite the the default toggle method to simply use toggle() instead of slideToggle(), which is a non-animated version that runs and completes immediately.

This makes writing the testing code a bit simpler too. (There are one or two bits that I did that didn’t feel too great in my opinion, such as the way I chose to expose the post toggle action, but that is perhaps for another day to sort out!)

To override the default implementation, you can redefine sideNotes.toggle in your code, something like this:

$.fn.sideNotes.toggle = function() {
    $(this).toggle().trigger('sideNoteToggled');
};

Example QUnit test code

So taking the above considerations this is what we might have in our QUnit unit test code:

This code block shows a test util object to help instrument the test, and then at the bottom, an example of a mock object to replace the slideToggle() call.

(function(){
    var testUtils = {
        isExpandedCount : 0,
        isCollapsedCount : 0,
        
        defaultTestOptions : {
            selector : '#example .side-note',
            sideNoteOptions : {},
            speed : 'fast'
        },
        
        // a crude setup like method, which QUnit currently doesn't have
        init : function(options) {
            testUtils.reset();
    
            var opts = $.extend({ sideNoteOptions : {} }, testUtils.defaultTestOptions, options);
    
            // tests assume the relevant HTML is present on the page the test is running
            $(opts.selector)
                .bind('sideNoteToggled', testUtils.sideNoteToggled)
                    .sideNotes(opts.sideNoteOptions);
            
            return $(opts.selector);
        },
        
        sideNoteToggled : function(event, isExpanded) {
            isExpanded ? testUtils.isExpandedCount++ : testUtils.isCollapsedCount++;
            
            $(event.target).trigger('sideNoteToggleCaptured');
        },
        
        // until there is an explicit tear down method this will have to do
        reset : function() {
            $('#example .side-note').unbind('sideNoteToggled');
            testUtils.isExpandedCount = 0;
            testUtils.isCollapsedCount = 0;
        }
    };

    // mock the slideToggle. We are not testing that.
    function mockSideNoteToggle() {
        $(this).toggle();
        
        $.fn.sideNotes.toggled.call(this);
    }
    
    // keep the original implementation if we want to use it later
    $.fn.sideNotes.originalToggler = $.fn.sideNotes.toggle;

    // override the default toggle method with the mock
    $.fn.sideNotes.toggle = mockSideNoteToggle;
})();

We can then write unit tests, such as this one (note this code would go inside the anonymous function used above):

module("Single toggle tests");

test("Test side note is expanded when toggled", function() {
    var $sideNote = testUtils.init();

    $sideNote.each(function() {
        equals($(this).attr('aria-expanded'), 'false', "Side not starts as collapsed");
    });
    
    $('#example')
        .find('.side-note:eq(0)').bind('sideNoteToggleCaptured', function() {
            equals(testUtils.isExpandedCount, 1, "A node has been expanded");
            equals(testUtils.isCollapsedCount, 0, "No nodes have been collapsed after initialization");
            equals(this['aria-expanded'], true, "Node’s aria state is expanded");
        })
        .end()
            .find('.toggler > a:eq(0)').click();
            // calling click() is like simulating the user action to start the test
});

In the above example, the following is happening:

  1. The call to init() creates/initializes the sideNote plugin
  2. We then assert that the ARIA state reports each side note is not expanded
  3. We then find each side note and bind to the sideNoteToggleCaptured event (which is raised by the unit test utility shown earlier, not by the plugin itself)
  4. We then simulate a user action by finding the toggler and clicking it
  5. The simulated click() makes the plugin eventually trigger the sideNoteToggled event, which the testUtils object will catch.
  6. The test util will catch that event and update the various testing counters and itself trigger the sideNoteToggleCaptured event.
  7. Finally at this point, we can run our assertions, such as confirming the expected number of times the side note was expanded/collapsed and what the expected ARIA state should be (in this example seen by the use of the equals() function).

See the full example for more unit tests

The additional tests in the full example also show the side note being dynamically created per test run, so you don’t always have to have the HTML set up exactly as needed for every test, manually.

The unit test code in that example can probably be further improved by refactoring those test() functions into a helper function where you pass in callbacks to run when the test has completed, how many clicks you want to invoke, etc, but that is for another time!

Final plugin code

Here is the final plugin code (with some additional options not discussed above):

(function($) {
    $.fn.sideNotes = function(options) {

        // build main options before element iteration
        var opts = $.extend({}, $.fn.sideNotes.defaults, options);

        // iterate and process each matched element
        return $(this)
            .addClass('dynamic-side-note').hide()
            .attr('aria-expanded', false)
            .wrap('<div class="dynamic-side-note-container"></div>')
            .before('<' + opts.toggleElement + '><a class="toggler" href="#">' + opts.sideNoteToggleText +'</a></' + opts.toggleElement + '>')
            .parent(0)
                .find(opts.toggleElement + '.toggler > a')
                    .click(doToggle);

        // example of private method
        function doToggle() {
            $(this)
                .parents('.dynamic-side-note-container').eq(0)
                    .find('> .dynamic-side-note').each( function() {
                        $.fn.sideNotes.toggle.call(this, options);
                    });

            return false;
        }
    };

    // plugin defaults
    $.fn.sideNotes.defaults = {
        sideNoteToggleText : 'Side note:',
        speed : 'normal',
        toggleElement : 'h3'
    };

    // default implementation for the toggler (public. i.e. overrideable)
    $.fn.sideNotes.toggle = function(options) {
        $(this).slideToggle(options.speed, $.fn.sideNotes.toggled);
    };

    // default callback when toggle completed (public. i.e. overrideable)
    $.fn.sideNotes.toggled = function() {
        this['aria-expanded'] = this['aria-expanded'] === true ? false : true;

        $(this).trigger('sideNoteToggled', this['aria-expanded']);
    };
})(jQuery);

Is the additional lines of code for a plugin worth it?

The plugin used in the unit tested example is larger in terms of lines of code than the very first attempt, above.

In this case, the lines of code is still quite small, and as plugins get even larger the percentage difference is likely to be small (the size difference is more noticeable in smaller code samples, such as this contrived side note example).

Minimizing and gzipping JavaScript, plus using far future expires header for better caching would further reduce the percentage difference in file sizes of the two approaches.

Depending on your needs, this may be a reasonable trade-off in return for additional flexibility.

Summary

So, in many cases, jQuery code can be made reusable by making it into a plugin. Any time you find yourself writing a block of code, say inside a $(document).ready() block, consider converting it into a plugin and calling it.

When making the plugin, consider the following:

  • Provide various options for flexibility
  • Write unit tests to test your plugin
  • Trigger important events inside your plugin to aide unit testability, and in doing so, increase flexibility of your plugin even more.

When you get the hang of this, it is probably better to write the unit tests before the actual plugin code. This will help focus on what is needed and what the plugin needs to expose in terms of capability. To be honest, at times I have found it easier to retrofit a plugin with unit tests. I probably need to be a bit more disciplined to write tests first!

(While there are a number of enhancements that could be added to this, the main thing I’d probably do is rename the plugin to something more generic than just a side note toggler, but I will leave that for the reader to do, and remember to refactor the unit tests accordingly!)

Hope that is useful?

29 thoughts on “Turn your jQuery code into a richer, unit testable, plugin

  1. Hey, that is a pretty good post (but it took a while to digest).

    I like the point about using triggering events instead of passing in callbacks from plugin options. I’m going to change some of my plugins that do that.

    Not done any unit tests before, so will have to look at this more.

  2. @Kean, Cora Guo: Thanks!

    @Jörn: Thanks for that. I must have missed that… That looks to be handy. I’ll have to refactor the above example to take that into account when I can find a spare moment 🙂

  3. very useful words! Thanks a lot!

    I got a question:
    If you use slideToggle() like this:

    .slideToggle(function() {...});

    why does it lead unstable result in testing? I would guess the callback function will be invoked only when the action is done… why did you say the callback function might be finished before the sliding was done?

  4. @xzhang: From what I was seeing, calling slideToggle resulted in that function returning before the actual slide had completed. This meant the rest of the test ran before the actual test completed. Any callback from slideToggle would run after the rest of the test had executed.

    I assumed jQuery had to be using timeouts or intervals in the animation code behind slideToggle. Looking at the jQuery source code, unless I read incorrectly, setInterval is eventually called.

    I couldn’t see a way to easily wait for this. QUnit does have stop() and start() methods, but I couldn’t get those to work for me. Maybe there was an easier way to do this? (It looks like the documentation has been updated a bit since I last saw it so I will look again to see if there is another way to deal with this.)

    In any case, it helped drive out the ability to “mock” the call to slideToggle to abstract away that detail, so if the implementation of the actual slide changed, the tests for the rest of the plugin would still work.

  5. ah, I see. sorry I misunderstood your words :p

    i am not familiar with QUnit yet – will spend some time on it first. thanks again 😀

  6. Pingback: links for 2008-11-20 « Brent Sordyl’s Blog

  7. Pingback: Jacky's Blog

  8. Hello,

    You have mentioned QUnit and Selenium but do you know of an easy way to run QUnit tests together with the Selenium Core TestRunner ? So I would like to see myQUnit tests results integrated in my Selenium TestRunner.

    Thanks a million.

  9. @PierreR: Although I did not create Selenium tests for the above example, I did this for another set of plugins for work.

    If I recall correctly (as I have some time off right now, so don’t have access to the code, unfortunately!) I believe what I did was to first create separate Selenium tests for each plugin, and then created a Selenium Test Suite whereby I just opened each individual Selenium test in turn.

    In each individual test, I would open the HTML page that contains the QUnit test, and once finished, verify that some text was present on the page (the results bit about 0 tests failing).

    Hope that helps?

  10. From what I’m seeing the plugin code you wrote won’t work with these unit tests.
    The unit tests depend on testUtils being globally accessible, but the testUtils object is defined within the plugin function.
    You could probably rejigger things a bit to make that work though.

  11. @Paul: the links to the full examples actually work. I looked at the examples in the post again.

    I should have clarified that the test() methods that make use of the testUtil object are in the same anonymous function, so it is accessible (if you see the link to the full tests, that is how it is done). I will update the post shortly. Thanks for spotting that.

  12. Interesting article, you definitely helped me out. You do a good job articulating why longer lines of code is sometimes desired. I get that a lot when I try to help people decompose their designs. People will say “oh you just made it longer and more complex”.

    I have also found that after doing true TDD for a while (like a year), you can almost adopt a test last methodology (although I wouldn’t recommend it).. You just get better at working in small increments after doing true TDD.

  13. @Josh: thanks for the comment. Interesting point abotu TDD as a last methodology — yeah, often wouldn’t recommend it, but I read somewhere, and found sometimes myself, that for certain kind of UI things, it sometimes is useful to do a bit later

    e.g. if generating a ton of HTML and exploring as you go and then tweaking (usually thanks to IE!) then it may be saner to create the unit tests for that kind of stuff nearer to the end…

  14. @Josh: If you see the unit tests I used in the full example, I tried to break each test down to a reasonable test case/scenario. I dunno if that helps answer the question you had in that forum post?

  15. Pingback: Javascript Unit Testing, Test Pages (test classes). « Web Application Development, E-commerce, Sales & Marketing

  16. Hey, I don’t think we’re talking about the same thing. I took a look at JSunit and it has that feature. I just wrote an article and pinged back, it shows exactly what I meant and shows the “test page runner” i wrote for qunit. It execute your pages and aggregates their results in one place, although it doesn’t have the fancy progress bar that jsunit has.

  17. I have not gone through the entire post but it is very comprehensive and touches everything one needs to know. Amazing work!

  18. I was randomly just browsing sites having jQuery related posts and landed here. Glad that I did! I liked the way you have covered everything in detail, yet it is in an easy to understand manner. I don’t know if this will interest you. There is a site called u-2-me. There is a whole community of knowledge providers and knowledge seekers. You can actually engage in online teaching about jQuery. You would be great. Just thought I would let you know.

  19. Pingback: Interessante Links im Netz 12 | DaRaFF's Blog

  20. this is the best tutorial on unit test plugin I found
    After I read your post, I got so enthusiasm on making my code as plugin
    Thanks very much

  21. Pingback: jQuery教學資源 - 工作達人(Job Da Ren)

Comments are closed.