Manual:JavaScript unit testing/QUnit guidelines

Comparisons edit

Use comparing functions such as strictEqual() and propEqual(). Avoid passing expressions to ok().

Letting QUnit perform the comparison has several advantages:

  • Allows the test runner to display the difference between the expected and actual values. When using ok() it forces one to do inline expression, which loses this information. This is especially important when working with test reports generated externally (eg. from Jenkins or Karma) in which case the test output is all you have. (When running tests manually in a browser locally, one could set breakpoints etc.)
  • Passing an explicit expected value serves to self-document the expected result.
  • Using an explicit expected value avoids accidentally hiding regressions as result of type casting and non-strict comparisons.
// Not very useful, the comparison is done inline and ok()
// can only know about the result of the comparison
var x = 'foo';
assert.ok( x === 'bar' ); // Failed.
assert.ok( x === 'foo' ); // Passed.
// Useful! The unit test is given both values,
// allowing it also mention both in the output:
var x = 'foo';
assert.strictEqual( x, 'bar' ); // Failed. Expected: "bar". Result: "foo"
assert.strictEqual( x, 'foo' ); // Passed. Expected: "foo".

In cases where the exact value is too variable or doesn't matter, you can still make an explicit comparison by checking its type (in case of a function), or its length (in case of an array or string). For example:

// 1) Bad example:
// - Fails or passes without other information.
// - Tolerates too many outcomes that are not expected. 
assert.ok( mw.Example, 'Quux is defined' );
assert.ok( mw.foo.barName, 'Name is set' );

assert.strictEqual( typeof mw.Example, 'function', 'Quux is defined' );
// Failed. Expected: "function". Result: "undefined"
// Passed. Expected: "function".

assert.strictEqual( typeof mw.foo.barName, 'string', 'Name is set' );
// Failed. Expected: "string". Result: "number"
// Passed. Expected: "string".

When dealing with objects (such as arrays, object literals, or other objects) one can't use equal to compare the content. In such case one has to use propEqual, which will loop over the object and compare each key/value separately. For example:

var a = ['foo', 'bar'];
assert.equal(a, ['foo', 'bar'], 'The array');
assert.strictEqual(a, ['foo', 'bar'], 'The array');
// Failed, because objects are compared by identity.

assert.deepEqual(a, ['foo', 'bar'], 'The array.');
// Passed. Expected: ['foo', 'bar']

Test elements edit

The QUnit runner automatically ensures the <div id="qunit-fixture"></div> element exists and is cleaned by QUnit before each test. Use this for any elements that need to be connected to the document. Avoid appending nodes elsewhere in the document as they may be left behind and affect other tests.

Asynchronous edit

Tests that assert the outcome of an asynchronous method (or a method that returns a promise), should have their return value returned from the QUnit.test() function. This allows QUnit to automatically track the promise without manually having to connect it with assert.async. This has the benefit of also automatically asserting that the Promise is resolved, and failing the test with the rejection error if the promise was rejected. No manual assert.ok(true/false) statements needed.

If an asynchronous method is expected to be rejected rather than resolved, one can use assert.rejects(), which behaves the same as returning a Promise to QUnit.test, except that it expects rejection rather than resolution.

Lastly, if a test is asynchronous but does not naturally provide a Promise, you can use assert.async() to manually track it. This is preferred over creating custom Promise or Deferred wrappers.

Data sourcing/seeding in Ajax requests edit

Because QUnit tests shouldn't depend by the data registered on the back-end (unless the test is not meant to verify a specific data-set available by default upon installation) the Ajax calls performed during tests requests should be handled using the following strategy:

  • if the result of an Ajax request only depends from the data provided by the client, or additional data retrieved on the server do not depend by a specific data-set, then a real Ajax request can be performed during the test, and the test can be verified either using assert.async in conjunction with done or using a trigger
  • if the result of an Ajax request depends by a specific data-set registered on the back-end, then the result should be either overridden with data provided by the QUnit function, or the success callback should be directly executed with the provided data without performing the Ajax request.

The rationale is that if a QUnit test relies on specific data-set server-side, unless they are seeded on the back-end as part of the test, and unless such data is not installed by default, in a different environment the Javascript tests will fail as long as the same data-set won't be found. This is of course true for every Ajax request involved from the test, also indirectly, so check carefully all the workflow of your script to prevent errors!

See also edit