tutorial

Monitoring Mocha Test Suites with Vigilmon: Detect When Tests Go Dark

Use Vigilmon heartbeat monitors to alert you when Mocha test suites stop running — before missing tests become missing test coverage and missing coverage becomes a production incident.

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


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 --spec glob that matches zero files (Mocha exits 0)
  • A --grep filter 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

  1. Log in to Vigilmon and click New Monitor → Heartbeat.
  2. Name it: Mocha Tests — main or Mocha Integration — nightly.
  3. Set the Expected interval: 2 hours for push-triggered suites, 25 hours for nightly.
  4. Set the Grace period: 20–30 minutes for short intervals, 1 hour for daily schedules.
  5. Save and copy the Ping URLhttps://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 #dev or #ci-alerts channel

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.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →