The xpcshell tool can be used to test certain kinds of functionality. Anything available to the XPCOM layer (through scriptable interfaces) can be tested with xpcshell. See Mozilla automated testing and pages tagged "automated testing" for pointers to more information.
Your first xpcshell-based test
Creating your first xpcshell-based test is easy. Create a file named test_first.js
(unit test filenames must start with test_
) with the following:
function run_test() { // do some complex action, check whether the action's results were what are expected here // this is just an example, so we assert that true == true ok(true); }
This doesn't really test anything, but it gives you an idea how you'd actually write a test. If you want to execute the test, you must first add it to the xpcshell.ini manifest. Add the test file to an existing set of tests (say, for example, netwerk/test/unit/
, but you should put it near the code it tests). You then execute your test using mach:
$ ./mach xpcshell-test netwerk/test/ ...failure or success messages are printed to the console...
Single tests may be run using mach like so:
$ ./mach xpcshell-test <path_to_test_file>
Adding to your test
The test is executed by the test harness by calling the run_test
function defined in the file. If this function runs to completion without throwing an exception, the test succeeds.
If you have a group of files in a directory and they all include some common code that needs to run before or after your tests, to set up resources for example, you can put one copy of that code in a common source file and add it to the head section of the manifest.
If you want to import a common module in your tests you can use resource://test
to load it. This special address always resolves to the current test folder.
Components.utils.import("resource://test/module.jsm"); // Import module.jsm that is in the same folder as current test.
XPCShell test functions
xpcshell tests have access to the following functions. They are defined in testing/xpcshell/head.js and testing/modules/Assert.jsm if you want to see how they work.
Test case registration and execution
add_task([condition, ]testGenerator)
- Add a generator to the list of tests that are to be run asynchronously. Whenever the generator
yield
s a Promise, the test runner waits until the promise is resolved or rejected before proceeding. As in Task.jsm, rejected promises are converted into exceptions, and resolved promises are converted into values. For tests that useadd_task()
, therun_test()
function is optional, but if present, it should also callrun_next_test()
to start execution of all asynchronous test functions. The test cases must not callrun_next_test()
, it is called automatically when the task finishes. See Async tests, below, for more information. Starting in Gecko (Firefox 40 / Thunderbird 40 / SeaMonkey 2.37), you can optionally specify a condition which causes the test function to be skipped; see Conditional test functions for details. add_test([condition, ]testFunction)
- Add a test function to the list of tests that are to be run asynchronously. Each test function must call
run_next_test()
when it's done. For tests that useadd_test()
,the run_test()
function is optional, but if present, it should also callrun_next_test()
to start execution of all asynchronous test functions. In most cases, you should rather use the more readable variantadd_task()
. See Async tests, below, for more information. Starting in Gecko (Firefox 40 / Thunderbird 40 / SeaMonkey 2.37), you can optionally specify a condition which causes the test function to be skipped; see Conditional test functions for details. run_next_test()
- Run the next test function from the list of asynchronous tests. Each test function must call
run_next_test()
when it's done.run_test()
should also callrun_next_test()
to start execution of all asynchronous test functions. See Async tests, below, for more information. do_register_cleanup(callback)
- Executes the function
callback
after the current JS test file has finished running, regardless of whether the tests inside it pass or fail. You can use this to clean up anything that might otherwise cause problems between test runs. - If
callback
returns aPromise
, the test will not finish until the promise is fulfilled or rejected (making the termination function asynchronous). - Cleanup functions are called in reverse order of registration.
do_test_pending()
- Delay exit of the test until do_test_finished() is called. do_test_pending() may be called multiple times, and do_test_finished() must be paired with each before the unit test will exit.
do_test_finished()
- Call this function to inform the test framework that an asynchronous operation has completed. If all asynchronous operations have completed (i.e., every do_test_pending() has been matched with a do_test_finished() in execution), then the unit test will exit.
Assertions and logging
ok, equal, notEqual, deepEqual, notDeepEqual, strictEqual, notStrictEqual, rejects, greater, greaterOrEqual, less, lessOrEqual
- These assertion methods are provided by Assert.jsm. It implements the CommonJS Unit Testing specification version 1.1, which provides a basic, standardized interface for performing in-code logical assertions with optional, customizable error reporting. It is highly recommended to use these assertion methods, instead of the ones mentioned below.
Assert.throws(callback, expectedException, optional message)
Assert.throws(callback, message)
- Asserts that the provided callback function throws an exception. The
expectedException
argument can be anError
instance, or a regular expression matching part of the error message (like inAssert.throws(() => a.b, /is not defined/
). do_check_eq(a, b)
Deprecated since Gecko 32.0- Call this function to assert that two objects are equal (using ==). If not equal, an exception is logged and the test case is halted.
do_check_neq(a, b)
Deprecated since Gecko 32.0- Call this function to assert that two objects are not equal (using !=). If equal, an exception is logged and the test case is halted.
do_check_true(expr)
Deprecated since Gecko 32.0- Call this function to assert that
expr
is equal totrue
. do_check_false(expr)
Deprecated since Gecko 32.0- Call this function to assert that
expr
is equal tofalse
. do_check_null(expr)
Deprecated since Gecko 32.0- Call this function to assert that
expr
is equal tonull
. do_print(messageText)
- Call this function to print text to the test's log file.
do_throw(messageText)
Deprecated since Gecko 32.0- Call this function to report an error and exit the test. The argument is a string that will be reported in the test's log file.
- Note: While
do_throw
can be caught by atry/catch
block, executing it will cause the test to fail when it completes.
Environment
do_get_file(testdirRelativePath, allowNonexistent)
- Returns an
nsILocalFile
object representing the given file (or directory) in the test directory. For example, if your test is unit/test_something.js, and you need to access unit/data/somefile, you would calldo_get_file('data/somefile')
. The given path must be delimited with forward slashes. You can use this to access test-specific auxiliary files if your test requires access to external files. Note that you can also use this function to get directories.Note: If your test needs access to one or more files that aren't in the test directory, you should install those files to the test directory in the Makefile where you specifyXPCSHELL_TESTS
. For an example, seenetwerk/test/Makefile.in#117
. do_get_profile()
- Registers a directory with the profile service and returns an
nsILocalFile
object representing that directory. It also makes sure that the profile-change-net-teardown, profile-change-teardown, and profile-before-change observer notifications are sent before the test finishes. This is useful if the components loaded in the test observe them to do cleanup on shutdown (e.g., places).Note:do_register_cleanup
will perform any cleanup operation before the profile and the network is shut down by the observer notifications. do_get_idle(
)
- By default XPCShell tests will disable the idle service, so that idle time will always be reported as 0. Calling this function will re-enable the service and return a handle to it; the idle time will then be correctly requested to the underlying OS. The idle-daily notification could be fired when requesting idle service. It is suggested to always get the service through this method if the test has to use idle.
do_get_cwd()
- Returns an
nsILocalFile
object representing the test directory. This is the directory containing the test file when it is currently being run. Your test can write to this directory as well as read any files located alongside your test. Your test should be careful to ensure that it will not fail if a file it intends to write already exists, however. load(testdirRelativePath)
- Imports the JavaScript file referenced by
testdirRelativePath
into the global script context, executing the code inside it. The file specified is a file within the test directory. For example, if your test is unit/test_something.js and you have another file unit/extra_helpers.js, you can load the second file from the first simply by callingload('extra_helpers.js')
. do_load_module(testdirRelativePath)
- Calls do_get_file(
testdirRelativePath
), then registers the returned file.
Utility
do_parse_document(path, type)
- Parses and returns a DOM document.
do_execute_soon(callback)
- Executes the function
callback
on a later pass through the event loop. Use this when you want some code to execute after the current function has finished executing, but you don't care about a specific time delay. This function will automatically insert ado_test_pending
/do_test_finished
pair for you.
do_timeout(delay, fun)
- Call this function to schedule a timeout. The given function will be called with no arguments provided after the specified delay (in milliseconds). Note that you must call
do_test_pending
so that the test isn't completed before your timer fires, and you must calldo_test_finished
when the actions you perform in the timeout complete, if you have no other functionality to test. (Note: the function argument used to be a string argument to be passed to eval, and some older branches support only a string argument or support both string and function.)
Conditional test functions
Starting in Gecko (Firefox 40 / Thunderbird 40 / SeaMonkey 2.37), you can use conditionals on individual test functions instead of entire files (which is covered in Adding conditions to a test). The condition is provided as an optional first parameter passed into add_test()
or add_task()
. The condition is an object which contains a function named skip_if()
, which is an arrow function returning a boolean value which is true
if the test should be skipped.
For example, you can provide a test which only runs on Mac OS X like this:
add_test({ skip_if: () => MOZINFO.os != "mac" }, function some_test() { // Test code goes here });
Since MOZINFO.os != "mac"
is true
only when testing on Mac OS X, the test will be skipped on all other platforms.
Note: Arrow functions are ideal here because if your condition compares constants, it will already have been evaluated before the test is even run, meaning your output will not be able to show the specifics of what the condition is.
Async tests
Asynchronous tests (that is, those whose success cannot be determined until after run_test
finishes) can be written in a variety of ways.
Task-based asynchronous tests
The easiest is using the add_task
helper. add_task
takes a JavaScript generator as its argument, which is run as in Task.jsm. add_task
tests are run automatically if you don't have a run_test
function.
function run_test() { // If you don't have any synchronous tests to call, you don't need to define run_test. // But if you define run_test, you must call run_next_test at the end of it. run_next_test(); } add_task(function* test_foo() { let foo = yield makeFoo(); // makeFoo() returns a Promise<foo> equal(foo, expectedFoo); }); add_task(function* test_bar() { let foo = yield makeBar(); // makeBar() returns a Promise<bar> equal(bar, expectedBar); });
Callback-based asynchronous tests
You can also use add_test
, which takes a function and adds it to the list of asynchronously-run functions. These functions are not run automatically; you must have a run_test
function which calls run_next_test
, and each function given to add_test
must also call run_next_test
at its end. You should normally use add_task
instead of add_test
, but you may see add_test
in existing tests.
function run_test() { run_next_test(); } add_test(function test_foo() { makeFoo(function callback(foo) { // makeFoo invokes a callback<foo> once completed equal(foo, expectedFoo); run_next_test(); }); }); add_test(function test_bar() { makeBar(function callback(bar) { equal(bar, expectedBar); run_next_test(); }); });
Other tests
We can also tell the test harness not to kill the test process once run_test()
is finished, but to keep spinning the event loop until our callbacks have been called and our test has completed. This can be achieved with do_test_pending()
and do_test_finished()
:
function run_test() { // Tell the harness to keep spinning the event loop at least // until the next do_test_finished() call. do_test_pending(); someAsyncProcess(function callback(result) { equal(result, expectedResult); // Close previous do_test_pending() call. do_test_finished(); }); }
Testing under Electrolysis
Since not all platforms support multi-process (electrolysis, aka "e10s"), you need to put any e10s-specific tests in a separate directory, and only run them if MOZ_IPC is defined. See /netwerk/test/Makefile.in for an example. Note that any "head_" scripts you have in your usual tests directory will not be run automatically in the new directory (but you can write a simple wrapper to load them: see "head_channels_clone.js" in netwerk/test/unit_ipc).
By default xpcshell tests run in the parent process. If you wish to run test logic in the child, you have several ways to do it:
- Create a regular test_foo.js test, and then write a wrapper test_foo_wrap.js file that uses the run_test_in_child() function to run an entire script file in the child. This is an easy way to arrange for a test to be run twice, once in chrome and then later (via the _wrap.js file) in content. See /network/test/unit_ipc for examples. The run_test_in_child() function takes a callback, so you should be able to call it multiple times with different files, if that's useful.
- For tests that need to run logic in both the parent + child processes during a single test run, you may use the poorly documented SendCommand() function, which takes a code string to be executed on the child, and a callback function to be run on the parent when it has completed. You will want to first call do_load_child_test_harness() to set up a reasonable test environment on the child. SendCommand returns immediately, so you will generally want to use do_test_pending/do_test_finished with it. NOTE: this method of test has not been used much, and your level of pain may be significant. Consider option #1 if possible.
See the documentation for run_test_in_child() and do_load_child_test_harness() in testing/xpcshell/head.js for more information.
Platform-specific tests
Sometimes you might want a test to know what platform it's running on (to test platform-specific features, or allow different behaviors). Unit tests are not normally invoked from a Makefile (unlike Mochitests), or preprocessed (so not #ifdefs), so platform detection with those methods isn't trivial.
Runtime detection
Some tests will want to only execute certain portions on specific platforms. One approach that's been used is to look for the existence of platform-specific components or interfaces. It's a bit hackish, but it's simple and it works.
- For Windows:
var isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
- For OS X:
var isOSX = ("nsILocalFileMac" in Components.interfaces);
- For Linux:
var isLinux = ("@mozilla.org/gnome-gconf-service;1" in Components.classes);
Adding your tests to the xpcshell manifest
To add a test to an xpcshell manifest file, you simply put the test file name in square brackets [] on its own line in the file, like so:
#edit netwerk/test/unit/xpcshell.ini [DEFAULT] head = head_channels.js [test_first.js]
If you need access to things in the browser/ directory (eg additional XPCOM services that live there, add this to the appropriate section:
firefox-appdir=browser
If this is a new directory that doesn't have an xpcshell manifest file, create a new file named xpcshell.ini and then add the following to a moz.build file near that file in the directory hierarchy:
XPCSHELL_TESTS_MANIFESTS += ['path/to/xpcshell.ini']
Typically, the moz.build containing XPCSHELL_TESTS_MANIFESTS is not in the same directory as xpcshell.ini, but rather in a parent directory. Common directory structures look like:
- feature/moz.build
- feature/tests/xpcshell/xpcshell.ini
or
- feature/moz.build
- feature/tests/moz.build
- feature/tests/xpcshell/xpcshell.ini
Adding conditions to a test
Sometimes you may want to add conditions to specify that a test should be skipped in certain configurations, or that a test is known to fail on certain platforms. You can do this in xpcshell manifests by adding annotations below the test file entry in the manifest, for example:
[test_first.js] skip-if = os == 'win'
This example would skip running test_first.js
on Windows.
Note: Starting with Gecko (Firefox 40 / Thunderbird 40 / SeaMonkey 2.37), you can use conditionals on individual test functions instead of on entire files. See Conditional test functions above for details.
There are currently four conditionals you can specify:
skip-if
skip-if
tells the harness to skip running this test if the condition evaluates to true. You should use this only if the test has no meaning on a certain platform, or causes undue problems like hanging the test suite for a long time.
run-if
run-if
tells the harness to only run this test if the condition evaluates to true. It functions as the inverse of skip-if
.
fail-if
fail-if
tells the harness that this test is expected to fail if the condition is true. If you add this to a test, make sure you file a bug on the failure and include the bug number in a comment in the manifest, like:
[test_first.js] # bug xxxxxx fails-if = os == 'linux'
run-sequentially
run-sequentially
basically tells the harness to run the respective test in isolation. This is required for tests that are not "thread-safe". You should do all you can to avoid using this option, since this will kill performance. However, we understand that there are some cases where this is imperative, so we made this option available. If you add this to a test, make sure you specify a reason and possibly even a bug number, like:
[test_first.js] run-sequentially = Has to launch Firefox binary, bug 123456.
Manifest conditional expressions
For a more detailed description of the syntax of the conditional expressions, as well as what variables are available, see this page.
Running unit tests
Tests should be run using ./mach xpcshell-test path/to/tests/
. To run a single test, use ./mach xpcshell-test path/to/test.js
.
Running unit tests under a C++ debugger
Via --debugger and -debugger-interactive
You can specify flags when issuing the xpcshell-test
command that will cause your test to stop right before running so you can attach to xpcshell in a debugger (implemented in bug 382682).
Example:
$ ./mach xpcshell-test --debugger gdb --debugger-interactive netwerk/test/test_resumable_channel.js # js>_execute_test(); ...failure or success messages are printed to the console... # js>quit();
Debugging Electrolysis (e10s) xpcshell tests
Under e10s you have two processes. You can debug the chrome process, the child process, or both at the same time.
To debug the chrome process
simply use the --debugger-interactive and --debugger flags as described above.
To debug the child process
The child process is where your xpcshell test's JS code is being run, so it is often--but not always--the process you're interested in. To debug the child, set MOZ_DEBUG_CHILD_PROCESS=1 in your environment (or on the command line) and run the test, and you will see the child process emit a printf with its process ID, then sleep. Attach a debugger to the child's pid, and when it wakes up you can debug it:
$ MOZ_DEBUG_CHILD_PROCESS=1 ./mach xpcshell-test test_simple_wrap.js CHILDCHILDCHILDCHILD debug me @13476
To debug both parent and child processes
Use MOZ_DEBUG_CHILD_PROCESS=1 to attach debuggers to each process. (For gdb at least, this means running separate copies of gdb, one for each process.)
Common problems
Events are not processed during test execution if not explicitly triggered. This sometimes causes issues during shutdown, when code is run that expects previously created events to have been already processed. In such cases, this code at the end of a test can help:
let thread = gThreadManager.currentThread; while (thread.hasPendingEvents()) thread.processNextEvent(true);