Mocha Setup Guide
describe / it / before / after
const assert = require('assert');
const { UserService } = require('../src/UserService');
describe('UserService', function () {
let service;
let db;
before(async function () {
// Runs once before all tests in this suite
db = await connectTestDB();
});
after(async function () {
// Runs once after all tests in this suite
await db.close();
});
beforeEach(async function () {
// Runs before each test
service = new UserService(db);
await db.seed([{ id: 1, name: 'Alice' }]);
});
afterEach(async function () {
// Runs after each test
await db.clearAll();
});
it('finds user by id', async function () {
const user = await service.findById(1);
assert.strictEqual(user.name, 'Alice');
});
it('returns null for unknown id', async function () {
const user = await service.findById(999);
assert.strictEqual(user, null);
});
describe('create()', function () {
it('saves user to database', async function () {
const created = await service.create({ name: 'Bob' });
assert.ok(created.id);
});
});
});
.mocharc.yml / .mocharc.json
# .mocharc.yml
spec: 'test/**/*.test.js'
require:
- '@babel/register'
- 'test/helpers/setup.js'
timeout: 5000
retries: 2
reporter: spec
color: true
recursive: true
exit: true # force exit after tests complete
# Parallel execution
parallel: true
jobs: 4
---
# .mocharc.json (alternative)
{
"spec": "test/**/*.spec.{js,mjs}",
"timeout": 10000,
"reporter": "mocha-junit-reporter",
"reporterOptions": {
"mochaFile": "test-results/junit.xml"
},
"require": ["./test/setup.js"],
"exit": true
}
Reporters
# Built-in reporters
mocha --reporter spec # Default: detailed
mocha --reporter dot # Minimal dots
mocha --reporter min # Summary only
mocha --reporter tap # TAP protocol
mocha --reporter json # JSON output
mocha --reporter json-stream # Streaming JSON
mocha --reporter nyan # Nyan cat 🐱
# Third-party reporters
npm install mocha-junit-reporter
mocha --reporter mocha-junit-reporter \
--reporter-options mochaFile=results/junit.xml
npm install mochawesome
mocha --reporter mochawesome \
--reporter-options reportDir=report,inline=true
Timeout & Retry
describe('API tests', function () {
// Set timeout for entire suite
this.timeout(10000);
it('fetches data', async function () {
// Override per-test
this.timeout(3000);
const data = await fetchData();
assert.ok(data);
});
// Retry flaky tests
it('flaky network test', async function () {
this.retries(3); // retry up to 3 times
const result = await unstableService.call();
assert.strictEqual(result.status, 'ok');
});
// Skip a test
it.skip('not implemented yet', function () {
// ...
});
// Run only this test (exclusive)
it.only('focus on this one', function () {
assert.strictEqual(1 + 1, 2);
});
});
Root Hooks & Global Fixtures
// test/hooks.js — root hook plugin
exports.mochaHooks = {
async beforeAll() {
// Runs once before all test suites
await startMockServer();
},
async afterAll() {
await stopMockServer();
},
async beforeEach() {
// Runs before each test across all files
this.requestId = crypto.randomUUID();
},
};
// Register in .mocharc.yml
// require: ['test/hooks.js']
// Global fixtures (Mocha v8+)
// test/fixtures.js
exports.mochaGlobalSetup = async function () {
// Runs before all suites — no `this` context
process.env.TEST_DB_URL = await startTestDatabase();
};
exports.mochaGlobalTeardown = async function () {
await stopTestDatabase();
};
ESM Support
// .mocharc.yml for ESM
spec: 'test/**/*.test.mjs'
# No --require needed for native ESM
// test/math.test.mjs
import assert from 'node:assert/strict';
import { add, multiply } from '../src/math.mjs';
describe('math', () => {
it('adds numbers', () => {
assert.equal(add(2, 3), 5);
});
});
// Mixed CJS/ESM: use .mjs extension or "type": "module" in package.json
// package.json scripts
{
"scripts": {
"test": "mocha",
"test:watch": "mocha --watch",
"test:coverage": "c8 mocha"
}
}