Automated testing with Mocha

Automated tests show up all over this course from here on, and they run real projects too. Once you can read a test, you can read a spec — and a spec tells you what code is supposed to do before you ever open the implementation.

Why do we need tests?

When you write a function, you usually carry a mental model of it: these inputs should produce those outputs. multiply(2, 3) should be 6. A price formatter should turn 1999 into "$19.99". You know the shape of “correct” before you write a line.

During development you check that model by hand. Run the function, look at the result, compare it to what you expected. Type multiply(2, 3) in the console, see 6, move on. Something wrong? Fix the code, run again, look again. Repeat until it behaves.

That loop works, but it has a leak.

Picture building a function f. You write some code and try it: f(1) works, f(2) doesn’t. You fix the code, and now f(2) works. Done? Not quite — you never re-ran f(1), and your fix may have quietly broken it. That regression ships.

This is the everyday reality of programming, not a rare accident. While building a feature you hold a dozen use cases in your head at once. Expecting yourself to manually re-check every one of them after every edit is unrealistic, so fixing one thing while breaking another becomes normal.

Automated testing means the tests live as separate code, alongside the function. They call your function across many scenarios and compare each result to the expected value, in seconds, every time you ask.

Manual
edit code
re-check ONE case by hand
other cases: unchecked
Automated
edit code
run the whole suite
ALL cases checked
Manual testing checks one path at a time and forgets the rest; automated tests re-run every case on demand.

Behavior Driven Development (BDD)

We’ll approach testing through a technique called Behavior Driven Development, or BDD for short.

BDD folds three things into one artifact: tests, documentation, and examples. The same file that verifies your code also describes what it does and shows how to call it.

The fastest way to get this is to build something. So let’s develop a small function and let the tests lead.

Development of “multiply”: the spec

Say we want multiply(a, b) — multiply a by a non-negative integer b by adding a to itself b times, assuming b ≥ 0.

This is a toy on purpose. JavaScript already has the * operator (2 * 3 is 6), so nobody needs this function. The point is the flow: the same steps scale up to real tasks where no operator does the work for you.

Before writing any implementation, describe what the function should do. That description is called a specification, or spec. A spec pairs each use case with a test that proves it:

describe("multiply", function() {

  it("multiplies two numbers", function() {
    assert.equal(multiply(2, 3), 6);
  });

});

A spec is built from three kinds of blocks. Here’s what each one is for.

describe(“multiply”, …) ← the group
it(“multiplies two numbers”, …) ← one use case

assert.equal(multiply(2, 3), 6)  ← the check

describe groups related tests; it names a single use case; assert checks an actual value against an expected one.

describe("title", function() { ... }) — names the functionality under test. Here it’s multiply. Its job is to group the workers inside it, the it blocks.

it("use case", function() { ... }) — the title states, in plain English, one specific behavior. The second argument is a function that actually exercises that behavior.

assert.equal(value1, value2) — lives inside it. If the implementation is right, the it function runs to the end without throwing. The assert.* family is how you check that. assert.equal compares its two arguments and throws an error when they differ, so assert.equal(multiply(2, 3), 6) asserts that multiply(2, 3) produced 6. There are many other assertions, and we’ll reach for a few more later.

A spec isn’t just documentation you read — it runs. Executing it runs the code inside every it. You’ll see exactly that in a moment.

The development flow

BDD moves in a loop:

  1. Write an initial spec with tests for the most basic behavior.
  2. Write an initial implementation.
  3. Run a test framework — we’ll use Mocha — to execute the spec. While the code is incomplete, tests fail and errors show up. Fix until they pass.
  4. You now have a working first version with tests.
  5. Add more use cases to the spec. Some aren’t supported yet, so those tests fail.
  6. Back to step 3: extend the implementation until tests pass again.
  7. Repeat 3–6 until the function is complete.

The work is iterative. Spec, implement, go green, add more spec, go green again. When you stop, you’re left with both a finished function and the tests that pin its behavior in place.

write failing test
RED
write code to pass it
tests pass
GREEN
add next case
The red-green loop: a new failing test drives each round of implementation.

Step 1 is already done — we have the initial spec for multiply. Before writing the implementation, let’s wire up the libraries and run the spec, just to watch it fail (everything should, since there’s no code yet).

The spec in action

This course uses three JavaScript libraries for tests:

  • Mocha — the core framework. It supplies describe, it, and the runner that executes everything.
  • Chai — a big collection of assertions. It’s where assert.equal and its siblings come from; for now that one is all we need.
  • Sinon — for spying on functions and faking built-ins. We won’t touch it until much later.

All three work in the browser and on the server. Here we run them in the browser.

Here’s the full HTML page that loads the frameworks and the multiply spec:

<!DOCTYPE html>
<html>
<head>
  <!-- mocha's stylesheet, to render the results nicely -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css">
  <!-- mocha itself -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
  <script>
    mocha.setup('bdd'); // switch on the BDD interface (describe/it)
  </script>
  <!-- chai -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
  <script>
    // chai bundles a lot; expose assert as a global for convenience
    let assert = chai.assert;
  </script>
</head>

<body>

  <script>
    function multiply(a, b) {
      /* the code goes here, empty for now */
    }
  </script>

  <!-- the tests: describe("multiply", ...) etc. -->
  <script src="test.js"></script>

  <!-- Mocha renders results into this element -->
  <div id="mocha"></div>

  <!-- run the tests -->
  <script>
    mocha.run();
  </script>
</body>
</html>

The page breaks into five parts:

  1. The <head> pulls in the third-party libraries and the test styles.
  2. A <script> holds the function under test — here, the code for multiply.
  3. The tests themselves. We keep them in an external test.js that contains the describe("multiply", ...) block from above.
  4. The element <div id="mocha"> is where Mocha writes its report.
  5. mocha.run() kicks everything off.

The result runs live below. It uses a tiny stand-in for describe/it/assert so the report renders right here with no external libraries, but the shape is exactly what Mocha shows. Edit multiply and press Run to watch the outcome change:

interactiveRunning the spec with an empty multiply

The test fails, as expected. multiply has an empty body, so multiply(2, 3) returns undefined instead of 6, and assert.equal throws.

Initial implementation

Let’s write just enough to make the test pass:

function multiply(a, b) {
  return 6; // :) we cheat!
}

And now it’s green:

interactiveThe cheat passes

Improving the spec

That was a cheat, obviously. The function doesn’t compute anything — multiply(3, 4) would return 6, which is wrong — yet the suite is green.

This exact situation happens in real projects: tests pass while the code is broken. The tests aren’t lying; they’re just incomplete. The fix is more use cases, not fewer.

Let’s assert that multiply(3, 4) is 12. There are two ways to add it.

Option 1 — a second assert in the same it:

describe("multiply", function() {

  it("multiplies two numbers", function() {
    assert.equal(multiply(2, 3), 6);
    assert.equal(multiply(3, 4), 12);
  });

});

Option 2 — a separate it for each case:

describe("multiply", function() {

  it("2 multiplied by 3 is 6", function() {
    assert.equal(multiply(2, 3), 6);
  });

  it("3 multiplied by 4 is 12", function() {
    assert.equal(multiply(3, 4), 12);
  });

});

The difference matters. When an assert throws, its it block stops right there. In option 1, if the first assert fails you never learn whether the second would have passed — the first failure hides the rest.

One it, two asserts
assert 1 ✗ FAIL
assert 2 — never runs
Two its
it 1 ✗ FAIL
it 2 ✓ still reports
One assert per it means every case reports independently; stacking asserts hides everything after the first failure.

Separate tests give you more information per run, so option 2 wins. It also lines up with a broader guideline.

We’ll go with the two-test version. The result — notice the second it still reports on its own line even though the first passed:

interactiveTwo its: one passes, one fails

The second test fails, exactly as it should — the function always returns 6, but the assert wants 12.

Improving the implementation

Time for a real implementation:

function multiply(a, b) {
  let result = 0;

  for (let i = 0; i < b; i++) {
    result += a;
  }

  return result;
}

To gain confidence, let’s check more than two inputs. Instead of hand-writing an it for every value, we can generate them in a loop:

describe("multiply", function() {

  function makeTest(a) {
    let expected = a * 3;
    it(`${a} multiplied by 3 is ${expected}`, function() {
      assert.equal(multiply(a, 3), expected);
    });
  }

  for (let a = 1; a <= 5; a++) {
    makeTest(a);
  }

});

makeTest(a) builds one it that checks multiply(a, 3) against a * 3, and the for loop calls it for a from 1 to 5. Because it just registers a test when it runs, calling it inside a loop is a normal way to produce a family of similar cases. The result — five tests from one helper, all green:

interactiveReal multiply, tests generated in a loop

Nested describe

We’re about to add more tests. First, notice that makeTest and its for loop belong together: they exist only to check how multiply handles a factor of 3, and no other test needs makeTest. When a helper and its loop share one narrow purpose, group them.

Grouping is what a nested describe is for:

describe("multiply", function() {

  describe("multiplies a by 3", function() {

    function makeTest(a) {
      let expected = a * 3;
      it(`${a} multiplied by 3 is ${expected}`, function() {
        assert.equal(multiply(a, 3), expected);
      });
    }

    for (let a = 1; a <= 5; a++) {
      makeTest(a);
    }

  });

  // ... more tests to follow here, both describe and it can be added
});

The inner describe creates a subgroup. Mocha renders that structure as indented headings, so the report mirrors the nesting in your code:

interactiveNested describe shows as indented groups

Later you can add more it and describe blocks at the top level, each with its own helpers. Because makeTest is declared inside the nested describe, those other blocks can’t see it — it stays scoped to the group that needs it.

describe(“multiply”)
describe(“multiplies a by 3”)
makeTest(a)  // private here
it(“1 multiplied by 3 is 3”)
it(“2 multiplied by 3 is 6”)
… up to a = 5
describe(… future group …)
cannot see makeTest
Nested describe blocks form a tree; helpers declared inside a group are private to that group.
before  (runs once)
beforeEach
it(“test 1”)
afterEach
beforeEach
it(“test 2”)
afterEach
after  (runs once)
before/after bracket the whole group once; beforeEach/afterEach wrap each individual test.

Extending the spec

The core of multiply works. First iteration done. After the champagne, let’s harden it.

Recall the contract: multiply(a, b) is meant for positive integer b. What about a negative b, or a fractional one like 1.5? JavaScript’s convention for a mathematically invalid result is to return NaN, so we’ll follow it.

BDD says the behavior goes into the spec first:

describe("multiply", function() {

  // ...

  it("for negative b the result is NaN", function() {
    assert.isNaN(multiply(2, -1));
  });

  it("for non-integer b the result is NaN", function() {
    assert.isNaN(multiply(2, 1.5));
  });

});

With the new tests added:

interactiveNaN cases fail against the guard-less multiply

These two fail, because the current implementation doesn’t handle those inputs. That’s the BDD rhythm on purpose: write the failing test, then write the code that satisfies it.

To feel why that distinction matters, try comparing a value with the two assertions side by side. Type any value, then see which check passes — note that NaN slips past equal (nothing equals NaN, not even NaN) but is caught by isNaN:

interactiveequal vs isNaN on the same value

Now the implementation needs two guard clauses:

function multiply(a, b) {
  if (b < 0) return NaN;
  if (Math.round(b) != b) return NaN;

  let result = 0;

  for (let i = 0; i < b; i++) {
    result += a;
  }

  return result;
}

Math.round(b) != b is the non-integer check: rounding a whole number leaves it unchanged, so any value that differs from its rounded form isn’t an integer. With both guards in place, the whole suite passes:

interactiveThe complete multiply: all green

Summary

In BDD the spec comes first and the implementation follows. When you finish, you’re holding both the spec and the code, and the spec earns its keep three ways at once.

Tests
prove the code behaves
Docs
describe / it titles say what it does
Examples
working calls show how to use it
One spec, three simultaneous roles.
  1. As tests — they guarantee the code does what it claims.
  2. As docs — the describe and it titles read as a description of the function’s behavior.
  3. As examples — each test is a runnable call showing how the function is used.

With a spec in hand, you can refactor, extend, or rewrite the function from scratch and immediately know whether it still behaves.

That safety net matters most in large projects, where a single function may be called from dozens of places. Change it, and there’s no realistic way to hand-verify every caller.

Without tests, teams fall into one of two traps:

  1. They make the change anyway. Something goes unchecked, and users hit the bug.
  2. Or, where mistakes are costly, people grow afraid to touch the function at all. The code ossifies, nobody wants to work in it, and the project stagnates.

Automated testing dissolves both traps. A well-covered project answers “did I break anything?” in seconds, right after any change.

Tested code also tends to have better architecture. Part of that is simple — code that’s easy to test is easy to change. But there’s a deeper reason: to test a function, you’re forced to give it a clear job with well-defined inputs and outputs. That pressure pushes you toward clean structure from the start.

It isn’t always smooth. Sometimes you can’t write the spec first because you don’t yet know how the thing should behave. Even so, testing generally makes development faster and steadier over the life of a project.

Many tasks later in this course arrive with tests already attached, so you’ll see plenty of real examples. Writing good tests takes solid JavaScript knowledge, and you’re still early in that journey — so for now you aren’t expected to author tests, but you should be able to read one, even a bit more involved than the ones here.