JavaScript for impatient programmers (ES2022 edition)
Please support this book: buy it or donate
(Ad, please don’t block.)

10 Getting started with quizzes and exercises



Throughout most chapters, there are quizzes and exercises. These are a paid feature, but a comprehensive preview is available. This chapter explains how to get started with them.

10.1 Quizzes

Installation:

Running the quiz app:

10.2 Exercises

10.2.1 Installing the exercises

To install the exercises:

10.2.2 Running exercises

10.3 Unit tests in JavaScript

All exercises in this book are tests that are run via the test framework Mocha. This section gives a brief introduction.

10.3.1 A typical test

Typical test code is split into two parts:

Take, for example, the following two files:

10.3.1.1 Part 1: the code

The code itself resides in id.mjs:

export function id(x) {
  return x;
}

The key thing here is: everything we want to test must be exported. Otherwise, the test code can’t access it.

10.3.1.2 Part 2: the tests

  Don’t worry about the exact details of tests

You don’t need to worry about the exact details of tests: They are always implemented for you. Therefore, you only need to read them, but not write them.

The tests for the code reside in id_test.mjs:

// npm t demos/quizzes-exercises/id_test.mjs
suite('id_test.mjs');

import * as assert from 'assert/strict'; // (A)
import {id} from './id.mjs'; // (B)

test('My test', () => { // (C)
  assert.equal(id('abc'), 'abc'); // (D)
});

The core of this test file is line D – an assertion: assert.equal() specifies that the expected result of id('abc') is 'abc'.

As for the other lines:

To run the test, we execute the following in a command line:

npm t demos/quizzes-exercises/id_test.mjs

The t is an abbreviation for test. That is, the long version of this command is:

npm test demos/quizzes-exercises/id_test.mjs

  Exercise: Your first exercise

The following exercise gives you a first taste of what exercises are like:

10.3.2 Asynchronous tests in Mocha

  Reading

You may want to postpone reading this section until you get to the chapters on asynchronous programming.

Writing tests for asynchronous code requires extra work: The test receives its results later and has to signal to Mocha that it isn’t finished yet when it returns. The following subsections examine three ways of doing so.

10.3.2.1 Asynchronicity via callbacks

If the callback we pass to test() has a parameter (e.g., done), Mocha switches to callback-based asynchronicity. When we are done with our asynchronous work, we have to call done:

test('divideCallback', (done) => {
  divideCallback(8, 4, (error, result) => {
    if (error) {
      done(error);
    } else {
      assert.strictEqual(result, 2);
      done();
    }
  });
});

This is what divideCallback() looks like:

function divideCallback(x, y, callback) {
  if (y === 0) {
    callback(new Error('Division by zero'));
  } else {
    callback(null, x / y);
  }
}
10.3.2.2 Asynchronicity via Promises

If a test returns a Promise, Mocha switches to Promise-based asynchronicity. A test is considered successful if the Promise is fulfilled and failed if the Promise is rejected or if a settlement takes longer than a timeout.

test('dividePromise 1', () => {
  return dividePromise(8, 4)
  .then(result => {
    assert.strictEqual(result, 2);
  });
});

dividePromise() is implemented as follows:

function dividePromise(x, y) {
  return new Promise((resolve, reject) => {
    if (y === 0) {
      reject(new Error('Division by zero'));
    } else {
      resolve(x / y);
    }
  });
}
10.3.2.3 Async functions as test “bodies”

Async functions always return Promises. Therefore, an async function is a convenient way of implementing an asynchronous test. The following code is equivalent to the previous example.

test('dividePromise 2', async () => {
  const result = await dividePromise(8, 4);
  assert.strictEqual(result, 2);
  // No explicit return necessary!
});

We don’t need to explicitly return anything: The implicitly returned undefined is used to fulfill the Promise returned by this async function. And if the test code throws an exception, then the async function takes care of rejecting the returned Promise.