Manual:JavaScript unit testing/QUnit guidelines

From MediaWiki.org
Jump to navigation Jump to search

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.

See also[edit]