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 describe
s) 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 it
s/describe
s to group together)
Nested describe
s and it
s 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
andassert.deepStrictEqual
. Do not useassert.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, withit
s for each type of test
- have one
- having one
- 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).
- type annotations are not required for
- 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)