ScreenshotNeo

BlogGuides

Mocha.js Tutorial: How to Test Node.js Applications

Install Mocha, write and run your first Node.js test, then choose reliable patterns for async code, hooks, modules, and configuration.

By the ScreenshotNeo team4 October 202610 min read

To test a Node.js application with Mocha, install Mocha as a development dependency, put test files in a test/ directory, write cases with describe and it, and run npx mocha. Use Node’s built-in node:assert for assertions. For asynchronous work, choose one completion style per test: a callback, a returned Promise, or async/await.

This tutorial uses ES modules in its examples. Mocha v12.0.0 documents a Node.js requirement of ^20.19.0 || >=22.12.0; check the current guide if your project uses another Mocha version. See the official Mocha getting started guide.

1. Check Node.js and install Mocha

Check the runtime and package manager available in your project:

node --version
npm --version

For Mocha v12, use a Node.js version matching ^20.19.0 || >=22.12.0, as documented by Mocha. Install Mocha locally so the project records its test runner as a development dependency:

npm install --save-dev mocha

Equivalent alternatives are pnpm add --save-dev mocha and yarn add --dev mocha. The remaining examples use npm commands.

2. Write and run a first test

Create test/math.test.js. Because this example uses ESM imports in a .js file, the project’s package.json must contain "type": "module". The example function is illustrative application code included in the test file:

import assert from 'node:assert/strict';
import { describe, it } from 'mocha';

function add(a, b) {
  return a + b;
}

describe('add', function () {
  it('returns the sum of two numbers', function () {
    assert.equal(add(2, 3), 5);
  });
});

Run the suite from the project root:

npx mocha

Mocha discovers tests in test/ by default. A passing result is reported by the runner; the exact formatting can depend on its reporter and version.

Add a standard npm script if you want the project’s usual test command:

{
  "type": "module",
  "scripts": {
    "test": "mocha"
  },
  "devDependencies": {
    "mocha": "^12.0.0"
  }
}

Keep the dependency version that your package manager actually installed; the version shown above is an example of a package manifest entry, not a recommendation to pin every project to that version. Then run npm test.

3. Choose an asynchronous test pattern

Mocha waits for asynchronous tests when you signal completion in one supported way. Pick the form that matches the API under test.

Callback API: call done

Use the callback form when the function being tested reports completion through a callback. Pass an error to done to fail the test:

import assert from 'node:assert/strict';
import { describe, it } from 'mocha';

function readValue(callback) {
  setTimeout(() => callback(null, 'ready'), 10);
}

describe('readValue', function () {
  it('returns the value through its callback', function (done) {
    readValue((error, value) => {
      if (error) return done(error);

      try {
        assert.equal(value, 'ready');
        done();
      } catch (assertionError) {
        done(assertionError);
      }
    });
  });
});

The callback implementation here is illustrative. In application code, make sure errors reach the callback and that every path completes or fails; otherwise the test will eventually time out.

Promise API: return the Promise

If the operation returns a Promise, return it from the test so Mocha can wait for it:

it('resolves to the expected value', function () {
  return Promise.resolve('ready').then((value) => {
    assert.equal(value, 'ready');
  });
});

Async function: use async/await

For a Promise-based API, an async test is often the clearest form:

it('awaits the operation', async function () {
  const value = await Promise.resolve('ready');
  assert.equal(value, 'ready');
});

Errors thrown by an awaited operation or assertion reject the async test and fail it. The same completion patterns work in asynchronous hooks.

Do not combine done() with a returned Promise or an async test. Those are two completion signals. Mocha reports an overspecified completion error when a test uses both.

4. Set up and clean up with hooks

The BDD interface provides four common hooks. before and after run once for their suite; beforeEach and afterEach run around every test in that suite. Prefer per-test setup when a test needs an independent fixture; use once-per-suite setup for resources that are genuinely shared.

import assert from 'node:assert/strict';
import { after, afterEach, before, beforeEach, describe, it } from 'mocha';

 describe('a service', function () {
  let fixture;

  before(async function () {
    // Illustrative: connect to a test resource once for this suite.
  });

  after(async function () {
    // Illustrative: close the shared resource after this suite.
  });

  beforeEach(async function () {
    fixture = { status: 'new' };
  });

  afterEach(async function () {
    // Illustrative: remove per-test state if the test created external data.
  });

  it('starts with a new fixture', function () {
    assert.equal(fixture.status, 'new');
  });
});

Remove the leading space before describe if copying the example literally; it is not significant to JavaScript syntax. The fixture and resource operations are placeholders, not a database implementation. Keep hooks scoped to the suite that needs them when possible. For root-level hooks, Mocha recommends Root Hook Plugins in current documentation.

5. Use CommonJS or ES modules consistently

The main examples above use ESM. Mocha supports ESM test files either with the .mjs extension or with .js files in a package whose package.json declares "type": "module". The example imports assert from Node’s built-in node:assert/strict module.

For a CommonJS project, use a .cjs test file and require instead:

const assert = require('node:assert/strict');
const { describe, it } = require('mocha');

describe('addition', function () {
  it('adds two values', function () {
    assert.equal(2 + 3, 5);
  });
});

Do not mix module syntax casually: Node determines how a file is interpreted from its extension and package configuration. Mocha documents that watch mode does not support ESM test files. If you rely on watch mode, check the current Mocha documentation for the module format and workflow that fit your project.

6. Configure Mocha only when needed

npx mocha is enough for a basic suite. When the same settings should apply repeatedly, put them in a supported .mocharc file or the mocha property in package.json. For example, a JSON configuration can specify a test pattern and reporter:

{
  "spec": "test/**/*.test.js",
  "reporter": "spec"
}

Save that JSON as .mocharc.json. Mocha also documents JavaScript, CommonJS, ESM, YAML, and JSONC configuration files. Avoid adding settings until you have a need; a custom file pattern is useful when your project does not use the default test/ layout.

When settings overlap, precedence is: command-line options, MOCHA_OPTIONS, the config file, then the mocha property in package.json. Use a command-line flag for a one-off override and shared config for a team default. See the official configuration guide and CLI reference for current options and defaults.

Options to introduce selectively

  • Reporter: Mocha’s documented default is spec. Choose another reporter when its output format fits a CI system or team workflow.
  • Timeout: the documented default is two seconds. Increase it only when a legitimate operation needs longer, and consider whether a slow test is waiting on an external dependency.
  • Retries: retries are opt-in. They can help with some transient failures, but should not conceal nondeterministic tests or broken cleanup.
  • Parallel mode: --parallel runs test files in a worker pool. Check for shared files, ports, databases, or other state that can conflict when files run concurrently.
  • Watch mode: --watch reruns tests after changes. Mocha documents an ESM test-file limitation for watch mode.

Defaults and feature behavior can change between releases. Confirm them in the CLI documentation for the version installed in your project.

7. A practical workflow for application tests

  1. Start at a behavior boundary. Choose a function or module with an observable input and output. Test a behavior a caller depends on rather than private implementation details.
  2. Keep assertions explicit. Use Node’s strict assertion module or another assertion library your project already uses. Mocha runs tests and reports failures; it does not require a separate assertion package.
  3. Separate unit setup from external services. Use small in-memory fixtures for isolated logic. If a test needs a database, network, or filesystem, create and clean its state deliberately in hooks.
  4. Run the smallest relevant test while editing. Use Mocha’s file selection options as documented for your installed version, then run the complete suite before relying on the change.
  5. Keep CI and local commands aligned. Put the shared invocation in an npm script or checked-in config so developers and automation use the same defaults.

8. Troubleshooting common Mocha failures

Symptom Likely cause What to do
mocha: command not found or command is unavailable Mocha is not installed in this project, or the command is not being run through the project package manager. Install it with npm install --save-dev mocha, run npx mocha from the project root, or use the configured package script.
Syntax error around import or require The test’s module syntax does not match its extension or package module setting. Use .mjs or "type": "module" for ESM, or use CommonJS syntax in a CommonJS test file.
No tests found The files are outside Mocha’s default discovery location or do not match the configured pattern. Put tests under test/, or set a matching spec path in the CLI/config. Check spelling and current working directory.
Test times out An async callback never completes, a Promise never settles, or the operation takes longer than the configured timeout. Ensure every callback path calls done or done(error); return or await the real Promise; inspect hanging external work before changing the timeout.
“Resolution method is overspecified” The test both returns a Promise and invokes done(). Use only one completion style in that test: callback, returned Promise, or async function.
Tests pass alone but fail together Tests may share mutable state, leak resources, depend on order, or fail to clean up. Reset per-test fixtures in beforeEach, release resources in after/afterEach, and avoid relying on execution order.
Tests fail only in parallel mode Files may contend for shared state such as a port, file, or external fixture. Isolate resources per worker or run those tests without parallel mode; do not assume files execute sequentially.
ESM tests do not rerun in watch mode Mocha documents that watch mode does not support ESM test files. Use a workflow supported by your current Mocha version, or keep ESM tests out of watch mode.
A setting appears ignored A higher-precedence source overrides it. Check command-line flags first, then MOCHA_OPTIONS, config file, and finally the package.json mocha property.

9. Performance, reliability, and cost

Mocha is a test runner installed as a development dependency. For a basic unit suite, test runtime is mostly determined by the code and external work in the tests; no performance benchmark is claimed here. Keep tests focused, avoid unnecessary external setup, and consider parallel execution only after checking that files do not compete over shared resources.

Reliability comes from deterministic tests and cleanup: each case should establish the state it needs, asynchronous work should always settle, and external resources should be released even when a test fails. Retrying a test can be appropriate for a known transient boundary, but repeated retries can also hide a flaky test.

The documented setup adds Mocha as a development dependency; it does not require a paid testing service. The examples use Node’s built-in assertions, so they do not add an assertion library. Any separate CI, database, or hosted service costs depend on the project and are outside this tutorial’s source material.

10. Screenshot website output in a Mocha workflow

If an application feature renders web pages, you may want to capture the output as an artifact for review. A raw screenshot API call is usually best kept separate from a fast unit test; if you include it in an automated suite, account for network latency and external page changes. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its product page describes one GET request that returns a PNG, JPEG, WebP, or PDF.

Or skip the browser setup

After installing Mocha and writing the do-it-yourself test above, a single request can capture a page without setting up a browser in your project. See the ScreenshotNeo API documentation for the request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before a shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Sign up for 1,000 free screenshots a month, with no card required.

FAQ

Does Mocha include assertions?

Mocha runs tests and reports outcomes; the first example uses Node’s built-in node:assert. You can use another assertion library if your project needs it.

Can I use Mocha with TypeScript?

This guide covers JavaScript. TypeScript execution requires a compatible project setup; consult the current Mocha and TypeScript tooling documentation for the approach and version constraints you plan to use.

Should each test have its own fixture?

Use per-test setup when isolation matters or tests mutate state. Share setup once per suite only when sharing is safe and cleanup is reliable.

Where can I check current Mocha behavior?

Use the official getting started guide, asynchronous code guide, hooks guide, configuration guide, ESM guide, and CLI reference. Documentation can change; verify version-specific requirements when upgrading.