Profiles·Public

mocha

semver>=8.0.0 <12.0.0postconditions17functions11last verified2026-06-24coverage score100%

Postconditions: what we check

  • it · it-async-unhandled-rejection
    warning
    Whentest function is async or returns a Promise that rejects
    ThrowsUnhandledPromiseRejectionWarning
    Required handlingTest functions that return promises or use async/await MUST handle rejections. Without proper error handling, unhandled promise rejections in tests cause test failures to be reported incorrectly or crash the test runner. Use pattern: it('test', async () => { try { await operation(); } catch (e) { throw e; } }) or rely on Mocha's built-in async handling.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • before · before-async-unhandled-rejection
    warning
    Whenhook function is async or returns a Promise that rejects
    ThrowsUnhandledPromiseRejectionWarning
    Required handlingBefore hooks that return promises MUST handle rejections properly. Unhandled rejections in before hooks cause test suite failures without clear error messages. Use async/await or return promises that are properly handled.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • after · after-async-unhandled-rejection
    warning
    Whenhook function is async or returns a Promise that rejects
    ThrowsUnhandledPromiseRejectionWarning
    Required handlingAfter hooks that return promises MUST handle rejections properly. Unhandled rejections in cleanup hooks can leave resources in inconsistent states. Use async/await or return promises that are properly handled.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • beforeEach · beforeeach-async-unhandled-rejection
    warning
    Whenhook function is async or returns a Promise that rejects
    ThrowsUnhandledPromiseRejectionWarning
    Required handlingBeforeEach hooks that return promises MUST handle rejections properly. Unhandled rejections cause individual test failures without clear attribution.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • afterEach · aftereach-async-unhandled-rejection
    warning
    Whenhook function is async or returns a Promise that rejects
    ThrowsUnhandledPromiseRejectionWarning
    Required handlingAfterEach hooks that return promises MUST handle rejections properly. Unhandled rejections in cleanup can leave test environment in bad state.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3]
  • afterEach · aftereach-resource-leak
    warning
    Whenhook has async cleanup (server.close(), db.disconnect()) but doesn't await it
    ThrowsMocha hangs, won't exit (requires --exit flag)
    Required handlingAfterEach hooks with async cleanup MUST await completion or return promises. Common mistake: afterEach(() => { server.close(); }) instead of afterEach(async () => { await server.close(); }). Unclosed resources (servers, database connections, file handles) prevent Mocha from exiting. Frequency: 25-35% of resource cleanup bugs.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4]
  • describe · describe-must-be-sync
    error
    Whendescribe() callback is async or contains await
    ThrowsUndefined behavior - async code in describe not awaited
    Required handlingdescribe() callbacks MUST be synchronous. Mocha does NOT await async code in describe blocks. Common mistake: describe('Suite', async () => { const data = await fetchData(); ... }) - data will be undefined. Use before() hook for async setup: before(async () => { data = await fetchData(); }). Frequency: 15-20% of suite setup bugs.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[5]
  • Mocha.loadFilesAsync · load-files-async-syntax-error
    error
    WhenA test file contains a JavaScript or TypeScript syntax error. loadFilesAsync() catches SyntaxError during import and rethrows it with the filename prepended to the stack trace for better diagnostics.
    ThrowsSyntaxError annotated with filename: "SyntaxError[ @/path/to/test.js ] <original message>". The Promise rejects — no test files after the failing file are loaded.
    Required handlingCaller MUST attach a .catch() or use try/catch with await on loadFilesAsync(): // CORRECT: handle rejection before run() mocha.loadFilesAsync() .then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0)) .catch(err => { console.error('Failed to load test files:', err); process.exitCode = 1; }); // CORRECT: async/await style try { await mocha.loadFilesAsync(); mocha.run(failures => process.exitCode = failures ? 1 : 0); } catch (err) { console.error('Test file load failed:', err); process.exitCode = 1; } Without a .catch() or try/catch, a syntax error in any test file causes an UnhandledPromiseRejection that crashes the Node.js process.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6][7]
  • Mocha.loadFilesAsync · load-files-async-module-not-found
    error
    WhenA test file imports a module that does not exist (e.g., a typo in an import path, a package that was not installed, or a relative import to a file that was deleted or renamed). Node.js throws an ERR_MODULE_NOT_FOUND error during import.
    ThrowsError with .code === 'ERR_MODULE_NOT_FOUND' and message like "Cannot find module './missing-module'" or "Cannot find package 'not-installed'". The Promise rejects immediately — remaining test files are not loaded.
    Required handlingCatch the rejection from loadFilesAsync(). The error message identifies which module was not found. Fix the import path or install the missing package. mocha.loadFilesAsync() .then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0)) .catch(err => { if (err.code === 'ERR_MODULE_NOT_FOUND') { console.error('Missing dependency:', err.message); } process.exitCode = 1; });
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6][8]
  • Mocha.loadFilesAsync · load-files-async-unknown-extension
    error
    WhenA test file has an unrecognized file extension (e.g., .ts without ts-node or @swc-node/register registered, .tsx, .vue) and Node.js cannot load it via either require() or import(). Throws ERR_UNKNOWN_FILE_EXTENSION.
    ThrowsError with .code === 'ERR_UNKNOWN_FILE_EXTENSION'. Common when running .ts test files without a TypeScript transpiler registered. The error message is "Unknown file extension '.<ext>' for /path/to/test.ts".
    Required handlingRegister a TypeScript loader before calling loadFilesAsync(): // Option 1: ts-node/esm loader // node --loader ts-node/esm mocha.mjs // Option 2: @swc-node/register import '@swc-node/register'; await mocha.loadFilesAsync(); // Option 3: Set esmDecorator to transform the path await mocha.loadFilesAsync({ esmDecorator: (file) => /* transform */ }); Catch and provide a clear diagnostic message about the missing loader.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[8][9]
  • Mocha.run · run-already-running-throws
    error
    Whenmocha.run() is called while a previous test run is still in progress. The Mocha instance tracks a running flag and throws synchronously if run() is called twice. This can happen in programmatic orchestration that doesn't await the callback.
    ThrowsError: "Mocha instance is currently running. Cannot run while running." (synchronous throw)
    Required handlingOnly call run() once per Mocha instance per run cycle. Use a new Mocha() instance per test run if running tests multiple times (e.g., in watch mode). // CORRECT: wait for run to finish before running again mocha.run(failures => { if (failures) process.exitCode = 1; // Don't call run() again here — use a fresh instance });
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[10][11]
  • Mocha.run · run-already-disposed-throws
    error
    Whenmocha.run() is called on a Mocha instance that has already been disposed. By default, Mocha disposes itself (cleans function references) after each test run when cleanReferencesAfterRun is true (the default). Calling run() again on a disposed instance throws synchronously. This is a common mistake in programmatic usage where developers reuse the same Mocha instance across multiple test runs.
    ThrowsError with code ERR_MOCHA_INSTANCE_ALREADY_DISPOSED: "Mocha instance is already disposed, cannot start a new test run. Please create a new mocha instance. Be sure to set disable `cleanReferencesAfterRun` when you want to reuse the same mocha instance for multiple test runs."
    Required handlingFor multiple test runs, either: (a) Create a new Mocha() instance per run (simplest), or (b) Disable auto-dispose and call dispose() manually: // OPTION A: new instance per run (recommended) function runTests() { const mocha = new Mocha({ timeout: 5000 }); mocha.addFile('./test/suite.spec.js'); mocha.run(failures => process.exitCode = failures ? 1 : 0); } // OPTION B: reuse instance — disable auto-dispose const mocha = new Mocha({ cleanReferencesAfterRun: false }); mocha.addFile('./test/suite.spec.js'); mocha.run(failures => { // can call mocha.run() again safely mocha.unloadFiles(); mocha.run(failures2 => process.exitCode = failures2 ? 1 : 0); });
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[12][11]
  • Mocha.run · run-silent-failure-no-callback
    warning
    Whenmocha.run() is called without a callback function, or with a callback that does not set process.exitCode based on the failures count. In CI environments, the process will exit with code 0 even when tests fail, making the build green when it should be red.
    ThrowsDoes not throw. Failures are silently discarded. Process exits with code 0 (success) even when N tests failed — CI/CD pipelines report false success.
    Required handlingAlways provide a callback that checks the failures count and sets process.exitCode: // CORRECT: set exit code based on failures mocha.run(failures => { process.exitCode = failures ? 1 : 0; }); // ALSO CORRECT: explicit exit mocha.run(failures => { if (failures > 0) { console.error(`${failures} test(s) failed`); process.exit(1); } }); // WRONG: no callback — failures silently swallowed mocha.run(); // WRONG: callback ignores failures mocha.run(() => { /* no check */ });
    costlowin prodsilent failureusers seelost datavisibilitysilent
    Sources[6][13]
  • Mocha.parallelMode · parallel-mode-after-run-throws
    error
    WhenMocha.parallelMode() is called after mocha.run() has already been invoked. Once run() is called, the Mocha instance transitions out of the INIT state and parallelMode() can no longer be toggled. This is a setup-ordering mistake in programmatic usage — parallelMode must be configured before run() begins executing test files.
    ThrowsError with code ERR_MOCHA_UNSUPPORTED: "cannot change parallel mode after having called run()"
    Required handlingAlways call parallelMode() before run(): // CORRECT: configure parallel mode before running const mocha = new Mocha({ timeout: 10000 }); mocha.addFile('./test/**/*.spec.js'); mocha.parallelMode(true); // BEFORE run() mocha.run(failures => process.exitCode = failures ? 1 : 0); // WRONG: parallel mode after run() — throws ERR_MOCHA_UNSUPPORTED mocha.run(failures => { mocha.parallelMode(true); // too late — throws }); Note: parallelMode can also be set via the Mocha constructor options: new Mocha({ parallel: true }) This avoids the ordering issue entirely.
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[14][15]
  • Mocha.parallelMode · parallel-mode-in-browser-throws
    error
    WhenMocha.parallelMode() is called in a browser environment. Mocha detects the browser via a utils.isBrowser() check. Parallel mode spawns Node.js worker threads/child processes which are unavailable in browsers. Uncommon in typical usage but affects projects that use mocha programmatically in both Node.js and browser contexts (e.g., universal test harnesses or karma integration with the Mocha class directly).
    ThrowsError with code ERR_MOCHA_UNSUPPORTED: "parallel mode is only supported in Node.js"
    Required handlingCheck the environment before enabling parallel mode: if (typeof process !== 'undefined' && process.versions?.node) { mocha.parallelMode(true); } // Or just never call parallelMode() in browser-targeted test code. Alternatively, pass parallel: true in MochaOptions constructor only in Node-targeted builds: new Mocha({ parallel: isNode })
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
  • Mocha.runGlobalSetup · run-global-setup-fixture-rejects
    error
    WhenA globalSetup fixture function throws synchronously or returns a rejecting Promise. Mocha's runGlobalSetup() awaits each fixture in sequence inside _runGlobalFixtures(); the first failing fixture propagates its rejection up through the returned Promise. Inside mocha.run() the rejection is not caught — it surfaces as an UnhandledPromiseRejection. Programmatic callers (anyone awaiting mocha.runGlobalSetup() directly, e.g. custom CI runners that want to separate setup from test execution) must attach .catch() or wrap in try/catch.
    ThrowsWhatever the failing fixture function threw — typically an Error (e.g. "Failed to start test database", "Cannot connect to seed server"). Rejection origin is the first fixture in the registered order that fails; subsequent fixtures are NOT executed (the for-await loop short-circuits).
    Required handlingWhen calling runGlobalSetup() directly (programmatic CI runner), attach .catch() or wrap in try/catch: // CORRECT: programmatic, separated phases const mocha = new Mocha({ globalSetup: [bootDb, seedFixtures] }); try { const ctx = await mocha.runGlobalSetup(); mocha.addFile('./test/suite.spec.js'); await mocha.loadFilesAsync(); mocha.run(failures => process.exitCode = failures ? 1 : 0); } catch (err) { console.error('Global setup failed:', err); process.exitCode = 1; } When using mocha.run() (which auto-invokes runGlobalSetup), register a process-level unhandledRejection handler OR disable global setup if you want to manage it yourself: process.on('unhandledRejection', err => { console.error('Mocha global setup/teardown failed:', err); process.exitCode = 1; }); Failing silently here means tests run against an uninitialized environment (no DB, no seed data) and either crash or report misleading test failures that obscure the real root cause.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
  • Mocha.runGlobalTeardown · run-global-teardown-fixture-rejects
    error
    WhenA globalTeardown fixture function throws synchronously or returns a rejecting Promise. Mocha's runGlobalTeardown() awaits each fixture in sequence; the first failing fixture propagates its rejection up through the returned Promise. Inside mocha.run() the rejection becomes an UnhandledPromiseRejection. Common in CI when teardown attempts to stop a background service that has already crashed, or to disconnect a database whose pool was already drained — the error is real but easy to ignore because tests have already "passed".
    ThrowsWhatever the failing fixture function threw — typically an Error like "Connection already closed", "Container stop timed out", "ECONNREFUSED while stopping seed server". Subsequent teardown fixtures are NOT executed, which compounds the resource leak (e.g. if fixture #2 fails, fixture #3 which would have closed the DB pool never runs).
    Required handlingWhen calling runGlobalTeardown() directly, attach .catch() or use try/catch: try { await mocha.runGlobalTeardown(ctx); } catch (err) { console.error('Global teardown failed:', err); // Force-kill any remaining resources you know about await Promise.allSettled([db?.disconnect?.(), server?.close?.()]); process.exitCode = 1; } When using mocha.run() (auto-invokes teardown), register a process-level unhandledRejection handler so CI surfaces the failure instead of silently succeeding with a green-tests-but-leaked-resources outcome: process.on('unhandledRejection', err => { console.error('Mocha global teardown failed:', err); process.exitCode = 1; }); Silent teardown failures are a classic "the test suite passed but my CI runner hung for 6 minutes" symptom and an even more dangerous "tests passed but the next job in the pipeline saw stale state" symptom.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation
Source code

Research notes

Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.

Sources: mocha

Last Updated: 2026-02-27 Package Version: >=8.0.0 <12.0.0 Research Quality: ⭐⭐⭐⭐ (comprehensive)


Official Documentation


Async Test Patterns

Mocha supports THREE async patterns:

1. Async/Await (Recommended)

it('should complete async operation', async () => {
  const result = await fetchData();
  assert.equal(result, expected);
});

Advantages:

  • Clean, readable syntax
  • Automatic promise handling
  • Errors automatically caught by Mocha

2. Returning Promises

it('should complete async operation', () => {
  return fetchData().then(result => {
    assert.equal(result, expected);
  });
});

Critical: MUST return the promise. Forgetting return causes test to complete immediately without waiting.

3. Done Callback

it('should complete async operation', (done) => {
  fetchData((err, result) => {
    if (err) return done(err);
    assert.equal(result, expected);
    done();
  });
});

Critical: MUST call done() or done(err) in ALL code paths.


Common Production Bugs

Bug #1: Forgetting to Return Promises (MOST COMMON)

Frequency: 40-50% of promise-based tests

Symptom: Tests pass but don't actually test anything (false positives)

Example (WRONG):

it('should fetch data', () => {
  // ❌ Missing return - test completes immediately
  fetchData().then(result => {
    assert.equal(result, expected);
  });
});

Fix:

it('should fetch data', () => {
  // ✅ Correct - return the promise
  return fetchData().then(result => {
    assert.equal(result, expected);
  });
});

Reference: Testing Promises Using Mocha

Bug #2: Not Calling done() in Error Paths

Frequency: 30-40% of callback-based tests

Symptom: Tests timeout (default 2000ms) waiting for done()

Example (WRONG):

it('should handle callback', (done) => {
  fetchData((err, result) => {
    // ❌ Missing done(err) in error case
    if (err) throw err;
    assert.equal(result, expected);
    done();
  });
});

Fix:

it('should handle callback', (done) => {
  fetchData((err, result) => {
    // ✅ Correct - call done(err)
    if (err) return done(err);
    assert.equal(result, expected);
    done();
  });
});

Reference: Mocha Async Code

Bug #3: Throwing Assertions in Promise Catch Handlers

Frequency: 20-30% of mixed callback/promise code

Symptom: UnhandledPromiseRejectionWarning, test timeout

Example (WRONG):

it('should test callback with promises', (done) => {
  fetchWithPromise((err, result) => {
    // ❌ Throwing inside promise catch handler
    assert.equal(result, expected);
    done();
  }).catch(err => done(err));
});

Problem: Assertions throw errors inside promise chain, causing unhandled rejections

Fix:

it('should test callback with promises', (done) => {
  fetchWithPromise((err, result) => {
    try {
      // ✅ Wrap assertions in try-catch
      assert.equal(result, expected);
      done();
    } catch (error) {
      done(error);
    }
  }).catch(err => done(err));
});

Reference: GitHub Issue #2797

Bug #4: Mixing done() with Promise Rejection Testing

Frequency: 15-25% of tests

Symptom: Cannot test promise rejections properly, tests timeout

Example (WRONG):

it('should reject', (done) => {
  // ❌ Can't use done() for promise rejection tests
  promise.catch(err => {
    assert.equal(err.message, 'expected');
    done(); // Can't call done() after exception
  });
});

Fix:

it('should reject', async () => {
  // ✅ Use async/await for rejection tests
  await assert.rejects(async () => {
    await promise;
  }, { message: 'expected' });
});

Hook Async Handling

All hooks support the same three async patterns:

Before/After Hooks

// ✅ Async/await (recommended)
before(async () => {
  await setupDatabase();
});

// ✅ Return promise
before(() => {
  return setupDatabase();
});

// ✅ Done callback
before((done) => {
  setupDatabase((err) => {
    done(err);
  });
});

Critical: Unhandled errors in before hooks prevent ALL tests in suite from running.

BeforeEach/AfterEach Hooks

Same async patterns as before/after, but run before/after EACH test.

Common mistake: Not cleaning up properly in afterEach, causing test pollution.


Timeout Handling

Default Timeout

Mocha tests timeout after 2000ms by default.

Custom Timeout

it('should complete slow operation', function() {
  this.timeout(5000); // 5 second timeout
  return slowOperation();
});

Note: Arrow functions don't work with this.timeout():

// ❌ WRONG - arrow function can't access this
it('slow test', () => {
  this.timeout(5000); // Error!
});

// ✅ CORRECT - use function keyword
it('slow test', function() {
  this.timeout(5000);
});

Error Types

1. UnhandledPromiseRejectionWarning

When: Promise rejected but not handled

Cause: Forgetting to return promise or not using await

Impact: Test framework may not catch the error properly

2. Test Timeout

When: Test doesn't complete within timeout period

Common Causes:

  • Forgot to call done()
  • Forgot to return promise
  • Infinite loop or hung async operation

Error Message: Error: Timeout of 2000ms exceeded

3. Assertion Errors

When: Test assertion fails

Handled Correctly: When using async/await or returning promises

Problematic: When throwing inside promise catch handlers or forgetting done(err)


Best Practices

1. Prefer Async/Await

Cleanest and most reliable pattern:

it('should work', async () => {
  const result = await operation();
  assert.equal(result, expected);
});

2. Always Return Promises

If not using async/await:

it('should work', () => {
  return operation().then(result => {
    assert.equal(result, expected);
  });
});

3. Call done() in All Paths

For callback-based code:

it('should work', (done) => {
  operation((err, result) => {
    if (err) return done(err); // Don't forget error path!
    assert.equal(result, expected);
    done();
  });
});

4. Don't Mix Patterns

Avoid:

  • Mixing done() with promises
  • Mixing async/await with done()
  • Using both return and done()

Important Notes

  • Testing Framework: Mocha runs in test environment, not production
  • Security: Low security risk (development tool)
  • Modern Mocha: v8+ has better async handling than older versions
  • Error Handling: Primarily affects test reliability, not production runtime

Contract Rationale

Postconditions (async-unhandled-rejection)

Test functions and hooks that use promises or async/await can fail in ways that cause unhandled promise rejections. Without proper handling:

  1. Tests may pass when they should fail (false positives)
  2. Tests may timeout waiting for completion
  3. Unhandled rejections cause confusing error messages

Evidence:


Research Metadata

  • Research Date: 2026-02-27
  • Researcher: Claude Sonnet 4.5
  • Documentation Sources: 5 URLs
  • GitHub Issues Analyzed: 3+
  • Common Mistakes Documented: 4
  • Line Count: 300+ lines (target 100+ ✅)
Need a different package?
Request a profile