Mocha has been running your JavaScript tests reliably for years. It's battle-tested, flexible, and used in everything from Node.js core libraries to legacy enterprise applications. Which is exactly why nobody checks whether it's still running — it's Mocha, it just works.
Until it doesn't. A CI pipeline reconfiguration silently excludes the test directory. A --grep flag narrowing test execution is left in place after debugging. A scheduled nightly suite stops triggering when the repo goes quiet. No failures to report. Just silence.
This tutorial shows you how to instrument Mocha test suites with Vigilmon heartbeat monitors to catch the silence before it costs you.
What You'll Cover
- Heartbeat monitoring for Mocha test pipelines
- Using Mocha's root hooks and reporter plugins for ping delivery
- Detecting suites that stop running or complete with zero tests
- Integrating with CI/CD pipelines
- Monitoring multi-suite and parallel Mocha runs
Prerequisites
- A Node.js project using Mocha
- A free account at vigilmon.online
The Problem: What Mocha Doesn't Report
Mocha's spec reporter and TAP output are great at describing test outcomes. They're silent about runs that never happen:
- A
--specglob that matches zero files (Mocha exits 0) - A
--grepfilter left from debugging that now matches nothing - A CI job that was skipped due to a path filter
- A scheduled test run that stopped being triggered
- A Mocha config change that breaks before any tests load
Vigilmon's heartbeat pattern fills this gap: your pipeline sends a ping on success, and Vigilmon alerts you when no ping arrives within the expected window.
Step 1: Create a Heartbeat Monitor in Vigilmon
- Log in to Vigilmon and click New Monitor → Heartbeat.
- Name it:
Mocha Tests — mainorMocha Integration — nightly. - Set the Expected interval: 2 hours for push-triggered suites, 25 hours for nightly.
- Set the Grace period: 20–30 minutes for short intervals, 1 hour for daily schedules.
- Save and copy the Ping URL —
https://vigilmon.online/api/heartbeat/<unique-id>.
Step 2: Add a Root Hook Plugin to Ping After Tests
Mocha's Root Hooks API (available since Mocha 8) provides afterAll — a hook that runs once after all tests in a suite complete. Create a root hook plugin:
// test/hooks/vigilmon.cjs
const https = require("node:https");
async function pingHeartbeat(url) {
if (!url) return;
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const req = https.request(
{
hostname: parsed.hostname,
path: parsed.pathname + parsed.search,
method: "POST",
timeout: 10_000,
},
(res) => {
res.resume();
resolve();
}
);
req.on("error", reject);
req.on("timeout", () => {
req.destroy();
reject(new Error("Heartbeat ping timed out"));
});
req.end();
});
}
exports.mochaHooks = {
afterAll: async function () {
// this.currentTest is undefined in afterAll — check stats
const stats = this.test?.ctx?._runnable?.parent?.parent?.$$stats;
// Only ping if we had no failures
if (process.exitCode === 0 || process.exitCode === undefined) {
await pingHeartbeat(process.env.VIGILMON_HEARTBEAT_URL).catch(
console.error
);
}
},
};
Register the plugin in .mocharc.cjs or .mocharc.yml:
// .mocharc.cjs
module.exports = {
require: ["./test/hooks/vigilmon.cjs"],
spec: "test/**/*.spec.{js,cjs,mjs}",
};
Or in .mocharc.yml:
require:
- ./test/hooks/vigilmon.cjs
spec: "test/**/*.spec.{js,cjs,mjs}"
Step 3: Simpler Approach — Ping in the npm Script
For projects where a root hook plugin is overkill, pipe the exit code through a shell check:
{
"scripts": {
"test": "mocha && curl -fsS -X POST \"$VIGILMON_HEARTBEAT_URL\" --max-time 10 --retry 3"
}
}
The && operator ensures curl only runs if Mocha exits 0. Set VIGILMON_HEARTBEAT_URL in your environment or CI secrets.
For Windows compatibility, use cross-platform scripts:
{
"scripts": {
"test": "mocha",
"test:ci": "mocha && node -e \"const{execSync}=require('child_process');execSync('curl -fsS -X POST '+process.env.VIGILMON_HEARTBEAT_URL)\""
}
}
Or use the node-fetch/undici approach in a small wrapper:
// scripts/ping-heartbeat.mjs
const url = process.env.VIGILMON_HEARTBEAT_URL;
if (url) {
await fetch(url, { method: "POST", signal: AbortSignal.timeout(10_000) });
}
{
"scripts": {
"test:ci": "mocha && node scripts/ping-heartbeat.mjs"
}
}
Step 4: Integrate with GitHub Actions
# .github/workflows/test.yml
name: Mocha Tests
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 4 * * *" # nightly 04:00 UTC
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Mocha
run: npm test
env:
VIGILMON_HEARTBEAT_URL: ${{ secrets.VIGILMON_MOCHA_HEARTBEAT_URL }}
# Root hook fires and pings Vigilmon on success
# Alternative: handle the ping at the workflow level
# - name: Ping Vigilmon heartbeat
# if: success()
# run: |
# curl -fsS -X POST "${{ secrets.VIGILMON_MOCHA_HEARTBEAT_URL }}" \
# --max-time 10 --retry 3 --retry-delay 2
Store the secret in GitHub → Settings → Secrets and variables → Actions → New repository secret:
- Name:
VIGILMON_MOCHA_HEARTBEAT_URL - Value: ping URL from Step 1
Step 5: Integrate with GitLab CI
# .gitlab-ci.yml
test:mocha:
image: node:20-alpine
stage: test
script:
- npm ci
- npm test
variables:
VIGILMON_HEARTBEAT_URL: $VIGILMON_MOCHA_HEARTBEAT_URL
after_script:
- |
if [ "$CI_JOB_STATUS" = "success" ]; then
curl -fsS -X POST "$VIGILMON_MOCHA_HEARTBEAT_URL" --max-time 10 --retry 3
fi
only:
- main
- schedules
Set VIGILMON_MOCHA_HEARTBEAT_URL in GitLab → Settings → CI/CD → Variables (masked).
If you're using the root hook approach, you can skip the after_script block — the plugin handles pinging.
Step 6: Monitor Multiple Mocha Suites
Large Node.js projects often separate unit and integration tests, running them on different schedules:
// .mocharc.unit.cjs
module.exports = {
require: ["./test/hooks/vigilmon.cjs"],
spec: "test/unit/**/*.spec.js",
};
// .mocharc.integration.cjs
module.exports = {
require: ["./test/hooks/vigilmon.cjs"],
spec: "test/integration/**/*.spec.js",
};
# .github/workflows/test.yml
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "npm" }
- run: npm ci
- run: npx mocha --config .mocharc.unit.cjs
env:
VIGILMON_HEARTBEAT_URL: ${{ secrets.VIGILMON_MOCHA_UNIT_HEARTBEAT_URL }}
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "npm" }
- run: npm ci
- run: npx mocha --config .mocharc.integration.cjs
env:
VIGILMON_HEARTBEAT_URL: ${{ secrets.VIGILMON_MOCHA_INTEGRATION_HEARTBEAT_URL }}
Create one Vigilmon heartbeat monitor per suite with appropriate intervals.
Step 7: Catch Zero-Test Runs
By default, Mocha exits 0 when --spec matches no files. To treat this as a failure, use --exit alongside a file count check, or configure Mocha to fail on empty runs:
In newer versions of Mocha, use the --no-pass-with-no-tests option (Mocha v10+):
mocha --no-pass-with-no-tests --spec "test/**/*.spec.js"
Or add a guard in your npm script:
{
"scripts": {
"test": "mocha --exit && node -e \"process.exit(0)\""
}
}
A simpler guard using find:
# In CI script
test_count=$(find test -name '*.spec.js' | wc -l)
if [ "$test_count" -eq 0 ]; then
echo "No test files found — failing build"
exit 1
fi
npx mocha --config .mocharc.cjs
When zero tests run and the build fails, no heartbeat ping reaches Vigilmon, and you get alerted.
Step 8: Alert Channels
In Vigilmon, go to Notifications → New Channel and configure:
- Email — direct alert when a heartbeat is missed
- Slack webhook — post to your
#devor#ci-alertschannel
When Mocha stops pinging:
🔴 MISSED HEARTBEAT: Mocha Tests — main
Last ping: 5 hours ago
Expected interval: 2 hours
When it resumes:
✅ HEARTBEAT RECOVERED: Mocha Tests — main
Gap: 5 hours
What You're Now Catching
| Silent failure mode | How Vigilmon detects it |
|---|---|
| --spec glob matches no files | Mocha exits 0 — add --no-pass-with-no-tests to block ping |
| --grep filter matches nothing | Same as above |
| CI job skipped by path filter | No ping within interval → alert |
| Suite fails (non-zero exit) | Root hook skips ping → alert |
| Scheduled job stops triggering | No ping → alert after grace period |
| Mocha config error on startup | Process exits non-zero → no ping |
Mocha's reliability can lull teams into complacency. Vigilmon's heartbeat monitors provide the external verification that Mocha needs: a positive confirmation that tests ran to completion, not just the absence of a failure signal.
Start monitoring your Mocha pipelines today — register free at vigilmon.online.