The Road to QUnit 3
What’s new since QUnit 2.0?
As is our tradition, no significant features are introduced in major releases. The new features that you may informally associate with “QUnit 3” have been gradually in the QUnit 2.x series.
Step API
The Step API provides a complete and strict way to verify asynchronous or event-driven code. You use it via the assert.step() and assert.verifySteps() methods. It was introduced in QUnit 2.2.
By simply recording and verifying steps, you achieve comprehensive and strict coverage for async or even-driven code. It naturally observes what is called, how it is called, in which order, and how often. You can also detect unexpected steps, which are reported as test failures.
QUnit.test('example', function (assert) {
const finder = new WordFinder();
finder.on('start', () => assert.step('start'));
finder.on('data', (word) => assert.step('data: ' + word));
finder.on('end', () => assert.step('end'));
finder.on('error', (e) => assert.step('error: ' + e));
finder.process('Hello, 2026. Great!');
assert.verifySteps(['start', 'data: Hello', 'data: Great', 'end']);
});
test.todo()
QUnit 2.2 adds QUnit.test.todo() to natively recognise tests that intentionally fail because the implementation is not yet finished. QUnit 2.4 later added QUnit.module.todo() to mark a group of tests, e.g. a nested sub-module or entire module.
If you write tests before the feature implementation, e.g. as part of a TDD practice, this method encourages sharing a specification or design early on by committing it to source control. You can then collaborate on that feature, and gradually enable tests and make them pass.
assert.timeout()
Since QUnit 2.4, you can override the default timeout on a per-test basis with assert.timeout().
QUnit.test('wait for an async function', async function (assert) {
assert.timeout(500); // Timeout after 0.5 seconds
const result = await asyncAdder(5, 7);
assert.strictEqual(result, 12);
});
assert.rejects()
QUnit 2.5 adds assert.rejects() as asynchronous version of assert.throws(). It allows for a simple and readable way to match an expected error from any async function or rejected Promise. No more workarounds!
async function feedBaby (food) {
if (food === 'sprouts') {
throw new RangeError('Do not like');
}
return true;
}
QUnit.test('example', async function (assert) {
assert.true(feedBaby('apple'));
await assert.rejects(feedBaby('sprouts'), RangeError);
assert.true(feedBaby('cucumber'));
});
You can perform additional assertions on the rejection value via the return value, since QUnit 2.26:
QUnit.test('example', async function (assert) {
const p = feedMe();
const e = await assert.rejects(p, RangeError);
assert.deepEqual(e.somedata, { foo: 'bar' });
});
Performance Timeline
QUnit 2.7 adds integration with browser DevTools to understand where your tests spend time. This feature measures the duration of every test, and adds them to the Performance Timeline in Firefox Profiler or Chrome DevTools. It is enabled by default when running tests in a browser.
See also QUnit.reporters.perf.
QUnit Run
└── QUnit Module: Example
├── QUnit Test: apple
├── QUnit Test: banana
└── QUnit Test: citron
assert.true() and assert.false()
The new strict boolean assert.true() and assert.false() methods arrived in QUnit 2.11. These methods provide a shortcut to assert.strictEqual() with true or false as the expected value.
The new methods promote strict equality by providing an appealing alternative to assert.ok() and assert.equal(,true), which were shorter than assert.strictEqual() but involve type casting.
Data providers
QUnit 2.16 introduced QUnit.test.each() to generate multiple test cases from a single template and a data provider. This removes the need to duplicate code across many similar tests, and removes the need for ad-hoc loops and other (often, untested) custom abstractions.
assert.propContains()
QUnit 2.18 added assert.propContains() to partially compare an object against expected key-value pairs, whilst ignoring other properties. This complements the assert.propEqual() method.
QUnit.test('example', function (assert) {
const result = {
foo: 0,
vehicle: {
timeCircuits: 'on',
fluxCapacitor: 'fluxing',
engine: 'running'
},
quux: 1
};
assert.propContains(result, {
foo: 0,
vehicle: { fluxCapacitor: 'fluxing' }
});
});
assert.closeTo()
QUnit 2.21 introduced assert.closeTo(), which checks that a number is within a range or tolerance from an expected number.
QUnit.test('example', function (assert) {
// passing: x is between 0.299 and 0.301
const x = 0.1 + 0.2; // 0.30000000000000004
assert.closeTo(x, 0.3, 0.001);
// passing: 3.14159 is between 3.140 and 3.142
assert.closeTo(Math.PI, 3.141, 0.001);
// passing: y is between 2010 and 2014 inclusive
const y = 2014;
assert.closeTo(y, 2012, 2);
});
Conditional skip
QUnit 2.22 introduced QUnit.test.if() and QUnit.module.if() to automatically skip a test when a certain condition is false. For example, to skip a test for a feature that is not available in older browsers.
Features for test runners and plugins
Preconfiguration
QUnit 2.1 introduced support for a predefined QUnit.config.
Event emitter
QUnit 2.2 introduced the QUnit.on() event emitter, which lets you create custom reporters. These can be loaded in the browser, or in the QUnit CLI via --reporter.
QUnit.on('runEnd', (runEnd) => {
console.log(`Passed: ${runEnd.passed}`);
console.log(`Failed: ${runEnd.failed}`);
console.log(`Skipped: ${runEnd.skipped}`);
console.log(`Todo: ${runEnd.todo}`);
console.log(`Total: ${runEnd.total}`);
});
QUnit CLI
QUnit 2.3 introduced the QUnit CLI, with the --require option arriving in 2.6, and the --module option in 2.19.
Global hooks
QUnit 2.18 introduced QUnit.hooks to globally add beforeEach and afterEach hooks on all tests.
See also
Get ready for QUnit 3 with the QUnit 3.0 Upgrade Guide.
Check out the blog archive or repository changelog for a complete history.