I have a fairly large amount of code inside one of my Firefox extensions (Shopping Helper) . I also have always manually tested this code, which made changes hard to do. I recently did a major refactoring of the code, and have plans to add more features, so it was time to get serious about automating the testing, with the first step of adding a comprehensive unit test suite. Now, just about every language has a useful framework, usually called something like "FooUnit" (e.g., JUnit, PyUnit), so that's the sort of thing I wanted for my extension.
Short answer is that QUnit does not run as-is in the chrome context, but can be made to do so with a moderate amount of work (it took me about 5 hours). Since I have now done this work, no one else needs to do this, so for you, it will be relatively easy with all the details I give below.
First off, here are the files you need, which are based on version 1.11.0 of QUnit.
Chrome vs. Non-chrome Control
To not have to fork the QUnit code for the modifications, I defined a global variable QU_ISCHROME whose setting can be changed to easily switch to being used inside a Firefox extension in a chrome window instead of a browser window. This will default to 'false' to give the previously existing behavior and can be changed to 'true' when including in the XUL/chrome Firefox extension context. It should be a simple one value change to get the effect, though I did not check if it ran outside a XUL window.
Altering createElement() calls
Since QUnit creates HTML DOM nodes, and since it will live inside an XML/XUL window, when creating these nodes, we have to explicitly define the namespace when creating them because the XUL namespace is not the standard HTML tags. We do this by replacing all calls like this:
document.createElement( "tag" );
with something like this:
document.createElementNS( QU_HTMLNS, "html:tag" );
var QU_HTMLNS = "http://www.w3.org/1999/xhtml";
We add a convenience function QU_createElement() and wrap all node creation in it to keep the code cleaner.
Altering innerHTML Content Assignments
While doing an assignment of an HTML fragment to a node via the innerHTML property works fine in a browser window, this does not work well in a chrome window. Thus, we wrap this assignment in a helper function QU_setInnerHTML() so that when QU_ISCHROME is true, it uses a proper parser directly.
Altering innerHtml Content Fetching
Similar to the assignment problem of innerHTML, using it as a value does not work that will in the chrome context either, so we also wrap these in a helper function QU_getInnerHTML().
Extension Development Environment Notes
I package up my extension with a bash script that conditionally includes testing and debug code based upon a command line parameter. So all the QUnit code is not in the production build. This requires a bit of a customized extension environment though one could just manually add and remove the QUnit code if you did not want to have to replicate conditional building. When included by the build script, the code also insert a menu option to allow invoking the unit test window.
XUL Window Notes
Need to have the CSS includes after the XML declaration, but before the window itself.
The window XML object needs the attribute to ensure that HTML elements can be put inside it:
As with the normal QUnit usage, you need the special two "div" elements. The twist here is that the HTML tag names need to be preceded with "html:" to explicitly define their namespace.
Full Example XUL Window
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://myextension/skin/qunit-1.11.0.css" type="text/css"?>
title="My Extension Unit Test Console"
To test functions in the main extension within the QUnit XUl windo, we need access to the main window where the extension chrome code runs. I am not sure if there are better ways to do this, but this works by iterating over windows.
var parentWindow = null
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
var enumerator = wm.getEnumerator("navigator:browser");
var win = enumerator.getNext();
if ( ! win.document )
parentWindow = win;
You then can invoke functions from the extension with:
parentWindow.someFunctionName( someArg1, someArg2 );
QUnit CSS Changes (optional)
Inside the chrome windows, the CSS property "-moz-border-radius" does not apply, so it gives a warning.