The powerful, easy-to-use testing framework for JavaScript applications
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Event-driven architecture enabling monitoring of test lifecycle, capturing results, building custom reporting solutions, and integrating with external tools.
Register callbacks to listen for test lifecycle events.
/**
* Register event listener for test lifecycle events
* @param {string} eventName - Name of the event to listen for
* @param {Function} callback - Function to call when event occurs
*/
QUnit.on(eventName, callback)Usage Examples:
import QUnit from "qunit";
// Listen for test start events
QUnit.on("testStart", function(details) {
console.log(`Starting test: ${details.name}`);
});
// Listen for assertion events
QUnit.on("assertion", function(details) {
if (!details.result) {
console.error(`Assertion failed: ${details.message}`);
}
});
// Listen for test completion
QUnit.on("testEnd", function(details) {
console.log(`Test completed: ${details.name} (${details.status})`);
});Events related to overall test run lifecycle.
/**
* Fired when test run begins
* @typedef {Object} RunStartEvent
* @property {number} totalTests - Total number of tests to run
* @property {Array} modules - Array of modules (for legacy callbacks)
*/
QUnit.on('runStart', function(runSuite) {
// runSuite contains test run information
console.log('Test run started');
});
/**
* Fired when test run completes
* @typedef {Object} RunEndEvent
* @property {string} status - 'passed' or 'failed'
* @property {Object} testCounts - Test result counts
* @property {number} runtime - Total runtime in milliseconds
*/
QUnit.on('runEnd', function(runSuite) {
// runSuite contains final test results
console.log('Test run completed');
});Usage Examples:
QUnit.on('runStart', function(runSuite) {
console.log('Test run started');
console.log(`Total tests: ${QUnit.config.stats.testCount}`);
});
QUnit.on('runEnd', function(runSuite) {
console.log('Test run completed');
console.log(`Results: ${runSuite.testCounts.passed} passed, ${runSuite.testCounts.failed} failed`);
console.log(`Runtime: ${runSuite.runtime}ms`);
});Events related to test module execution.
/**
* Fired when module execution begins
* @typedef {Object} SuiteStartEvent
* @property {string} name - Module name
* @property {string} moduleId - Module ID
*/
QUnit.on('suiteStart', function(suite) {
console.log(`Starting module: ${suite.name}`);
});
/**
* Fired when module execution completes
* @typedef {Object} SuiteEndEvent
* @property {string} name - Module name
* @property {string} moduleId - Module ID
* @property {string} status - 'passed' or 'failed'
* @property {Object} testCounts - Test counts within module
* @property {number} runtime - Module runtime in milliseconds
*/
QUnit.on('suiteEnd', function(suite) {
console.log(`Module completed: ${suite.name}`);
});Usage Examples:
QUnit.on('suiteStart', function(suite) {
console.log(`Starting module: ${suite.name}`);
console.log(`Tests in module: ${suite.testCounts.total}`);
});
QUnit.on('suiteEnd', function(suite) {
console.log(`Module ${suite.name} completed`);
console.log(`Status: ${suite.status}`);
console.log(`Runtime: ${suite.runtime}ms`);
});Events related to individual test execution.
/**
* Fired when individual test begins
* @typedef {Object} TestStartEvent
* @property {string} name - Test name
* @property {string} testId - Test ID
* @property {string} module - Module name
* @property {boolean} [previousFailure] - Whether test failed in previous run
*/
QUnit.on('testStart', function(test) {
console.log(`Starting test: ${test.name}`);
});
/**
* Fired when individual test completes
* @typedef {Object} TestEndEvent
* @property {string} name - Test name
* @property {string} testId - Test ID
* @property {string} module - Module name
* @property {boolean} skipped - Whether test was skipped
* @property {boolean} todo - Whether test is a todo test
* @property {number} failed - Number of failed assertions
* @property {number} passed - Number of passed assertions
* @property {number} total - Total number of assertions
* @property {number} runtime - Test runtime in milliseconds
* @property {Array} assertions - Array of assertion objects (HTML Reporter use)
* @property {string} source - Test source location (getter)
*/
QUnit.on('testEnd', function(test) {
const status = test.failed > 0 ? 'failed' : 'passed';
console.log(`Test ${status}: ${test.name}`);
});Usage Examples:
QUnit.on('testStart', function(test) {
console.log(`Starting test: ${test.name}`);
console.log(`Module: ${test.module}`);
console.log(`Test ID: ${test.testId}`);
});
QUnit.on('testEnd', function(test) {
const status = test.failed > 0 ? 'FAILED' : 'PASSED';
console.log(`${status}: ${test.name}`);
console.log(` Passed: ${test.passed}, Failed: ${test.failed}, Total: ${test.total}`);
console.log(` Runtime: ${test.runtime}ms`);
if (test.skipped) {
console.log(' Status: SKIPPED');
} else if (test.todo) {
console.log(' Status: TODO');
}
});Events fired for each individual assertion.
/**
* Fired for each assertion
* @typedef {Object} AssertionEvent
* @property {boolean} passed - Whether assertion passed
* @property {any} [actual] - Actual value
* @property {any} [expected] - Expected value
* @property {string} message - Assertion message
* @property {string} [stack] - Stack trace for failed assertions
* @property {boolean} todo - Whether assertion is in a todo test
*/
QUnit.on('assertion', function(assertion) {
if (assertion.passed) {
console.log(`✓ ${assertion.message}`);
} else {
console.error(`✗ ${assertion.message}`);
if (assertion.stack) {
console.error(assertion.stack);
}
}
});Usage Examples:
QUnit.on('assertion', function(assertion) {
if (assertion.passed) {
console.log(`✓ ${assertion.message}`);
} else {
console.error(`✗ ${assertion.message}`);
if (assertion.actual !== undefined) {
console.error(` Expected: ${assertion.expected}`);
console.error(` Actual: ${assertion.actual}`);
}
if (assertion.stack) {
console.error(` ${assertion.stack}`);
}
}
});Events fired for uncaught errors and exceptions.
/**
* Fired for uncaught errors during test execution
* @typedef {Object} ErrorEvent
* @property {string} message - Error message
* @property {string} [source] - Error source location
* @property {string} [testName] - Test name when error occurred
* @property {string} [module] - Module name when error occurred
*/
QUnit.on('error', function(error) {
console.error('Uncaught error:', error.message);
if (error.source) {
console.error('Source:', error.source);
}
});Usage Examples:
QUnit.on("error", function(details) {
console.error("Uncaught error during test execution:");
console.error(`Message: ${details.message}`);
console.error(`Source: ${details.source}`);
if (details.testName) {
console.error(`Test: ${details.testName}`);
}
if (details.module) {
console.error(`Module: ${details.module}`);
}
});Build custom reporters using the event system.
Usage Examples:
// Custom JSON reporter
class JSONReporter {
constructor() {
this.results = {
tests: [],
modules: [],
summary: null
};
this.bindEvents();
}
bindEvents() {
QUnit.on("testEnd", (details) => {
this.results.tests.push({
name: details.name,
module: details.module,
status: details.status,
assertions: details.assertions.total,
runtime: details.runtime,
errors: details.errors
});
});
QUnit.on("suiteEnd", (details) => {
this.results.modules.push({
name: details.name,
status: details.status,
testCounts: details.testCounts,
runtime: details.runtime
});
});
QUnit.on("runEnd", (details) => {
this.results.summary = {
status: details.status,
testCounts: details.testCounts,
runtime: details.runtime
};
this.output();
});
}
output() {
console.log(JSON.stringify(this.results, null, 2));
}
}
// Initialize custom reporter
new JSONReporter();Monitor test progress and performance.
Usage Examples:
// Performance monitoring
const performanceMonitor = {
slowTests: [],
threshold: 1000, // 1 second
init() {
QUnit.on("testEnd", (details) => {
if (details.runtime > this.threshold) {
this.slowTests.push({
name: details.name,
module: details.module,
runtime: details.runtime
});
}
});
QUnit.on("runEnd", () => {
if (this.slowTests.length > 0) {
console.warn("Slow tests detected:");
this.slowTests.forEach(test => {
console.warn(` ${test.module} > ${test.name}: ${test.runtime}ms`);
});
}
});
}
};
performanceMonitor.init();QUnit supports the following events (defined in src/events.js):
/**
* Available event names:
* - 'error' - Uncaught errors (memory event)
* - 'runStart' - Test run begins
* - 'suiteStart' - Module execution begins
* - 'testStart' - Individual test begins
* - 'assertion' - Each assertion result
* - 'testEnd' - Individual test completes
* - 'suiteEnd' - Module execution completes
* - 'runEnd' - Test run completes (memory event)
*/
/**
* Memory events are replayed for late-registered listeners
* Events: 'error', 'runEnd'
*/QUnit also supports legacy callbacks for backwards compatibility:
/**
* Legacy callback functions (still supported but events preferred)
*/
QUnit.begin(callback) // Called before tests start
QUnit.testStart(callback) // Called when test starts
QUnit.log(callback) // Called for each assertion
QUnit.testDone(callback) // Called when test completes
QUnit.moduleStart(callback)// Called when module starts
QUnit.moduleDone(callback) // Called when module completes
QUnit.done(callback) // Called when all tests completeInstall with Tessl CLI
npx tessl i tessl/npm-qunit