CSE 331: Mocha

Mocha is the JavaScript test framework that we'll use in 331. It is one of many popular testing libraries in the JavaScript and TypeScript ecosystem.

This document provides some brief notes on the relevant syntax and structure of using the framework within 331. This is not a comprehensive overview of all of its features, but is intended to be enough to support usage in section and on homeworks.

Installing and Using Mocha

Mocha can be installed via npm (like the other libraries in our class). In HW4 onwards, we have added it to your package.json. Executing npm run test will run Mocha across all files in your project that end with _test.ts (e.g. number_test.ts, request_test.ts, ...).

To use Mocha's library functions, you should use exactly this import statement:

import * as assert from 'assert';

How Mocha Works

Assertions and Tests

In Mocha, an it "block" defines a test. Tests are comprised of one or more assertions.

When Mocha is invoked, it runs all tests within an it block. That block passes if (and only if) all of its assertions were true.

(an it block is really a function that takes two arguments: a string representing the name of the test, and a function whose body is executed when the test is run)

The most basic (passing) Mocha test is the following:

it("always passes", function() {
  assert.strictEqual(true, true);
})

The most basic (failing) Mocha test is the following:

it("always fails", function() {
  assert.strictEqual(true, false);
})

Describe

The describe "block" groups together multiple it blocks (or other describes) under one name. These are mostly used for organizational purposes: they help other programmers better read and understand your code, and structure the test output from Mocha.

(a describe block is really a function with similar parameters to an it block: a string that is the name of the block, and a function whose body is the list of its/describes to group together)

Nested describes and its are printed out with their messages. Picking good names can help format tests better. For example, this test structure:

describe("isPrime", function() {
  it("returns true for prime numbers", function() { /* ... */});

  it("returns false for composite numbers", function() { /* ... */});

  it("returns false for 1n", function() { /* ... */});
});

produces this output:

isPrime
  ✓ returns true for prime numbers
  ✓ returns false for composite numbers
  ✓ returns false for 1n

331 Mocha Conventions

  • only use assert.strictEqual and assert.deepStrictEqual. Do not use assert.equal.
  • structure your test files by:
    • having one _test.ts file per .ts file
    • having a top-level describe for the overall file/module itself
    • either:
      • have one it per function, for very simple functions
      • have one describe per function, with its for each type of test
  • do not use .only() or .skip() (more broadly, stick to the features in this document).
  • exceptions to our coding conventions just for test files (that end with _test.ts):
    • type annotations are not required for describe, it, or their arguments.
    • test bodies can use function instead of () => (you can just copy-paste this - don't worry about the semantics).
  • otherwise follow our coding conventions.

Mocha Tips and Tricks

Mocha has a command-line flag called --watch that keeps it running in a terminal, and automatically re-runs all your tests on save. To use this, execute:

$ npm run test -- --watch --extension ts

(the middle -- is important - this forwards --watch to Mocha, rather than passing it as a flag to npm)

To exit watch mode, use Ctrl or Cmd + C.

Worked Example: number

Assume that we're implementing a file called number.ts that exports two functions:

// number.ts

/** Returns the greatest common divisor of a and b, for positive a and b. */
export const gcd = (a: bigint, b: bigint): bigint => {
  /* ... */
};

/** Determines whether n is a prime number, for positive n. */
export const isPrime = (n: bigint): boolean => {
  /* ... */
};

We can write the following number_test.ts that structures the tests by module (number), function (isPrime and gcd), and then provides descriptive names for each category of test.

// number_test.ts

import * as assert from "assert";
import { gcd, isPrime } from "./number";

describe("number", function() {
  describe("isPrime", function() {
    it("returns true for prime numbers", function() {
      assert.strictEqual(isPrime(2n), true);
      assert.strictEqual(isPrime(3n), true);
    });

    it("returns false for composite numbers", function() {
      assert.strictEqual(isPrime(4n), false);
      assert.strictEqual(isPrime(100n), false);
    });

    it("returns false for 1n", function() {
      assert.strictEqual(isPrime(1n), false);
    });
  });

  describe("gcd", function() {
    it("returns 1n for numbers with no common factors", function() {
      assert.strictEqual(gcd(3n, 2n), 1n);
      assert.strictEqual(gcd(9n, 3n), 1n);
    });

    it("finds common divisors when they exist", function() {
      assert.strictEqual(gcd(12n, 9n), 3n);
    });
  });
});

Running npm run test then prints out a nice hierarchy of tests:

$ npm run test

  number
    isPrime
      ✓ returns true for prime numbers
      ✓ returns false for composite numbers
      ✓ returns false for 1n
    gcd
      ✓ returns 1n for numbers with no common factors
      ✓ finds common divisors when they exist

  5 passing (3ms)