The internals of the Mocha Javascript testing framework

In an attempt to extend the functionality of Mocha for a specific use case I was required to look into its source code.

I wanted to write the flow down so it was clearer to myself. Maybe someone else will find this useful.

Mocha

Firstly you have the Mocha 'object' which in many respects is a container for all the other constituent parts (outlined below).

An instance of Mocha is exposed as mocha. The mocha instance has some helper methods for setting up Mocha with the correct options. For example the interface with which you will write your tests (BDD/TDD/qUnit).

One method on the Mocha prototype is run. Calling this instantiates a Reporter and a Runner (amongst other things).

Runner

A Runner object runs yours tests. Mocha by default creates a base level suite which the runner is instantiated with.

Each suite requires a Context. This context references a Runnable, and provides various prototype methods to set context specific settings. Each Test is a Runnable, as is each Hook.

When you call mocha.run, it creates this runner, sets various things up and calls its run prototype method.

Reporter

A reporter programatically specifies how the test output will be shown. A suprising amount of Mocha's codebase is different types of reporter. You can output your results in 'Nyan Cat' format if you so wish :)

On creation the Mocha object loads a reporter based on the passed options. When you run mocha the Reporter instance is instantiated, passing the Runner in.

Hooks

Hooks are hooks. They are pieces of code that can be hooked in at different stages of the process. Mocha has hooks such as before(), after(), beforeEach(), and afterEach().

Behind the scenes, they are set up in exactly the same way as Tests.

Running your tests

Mocha relies heavily on nodes EventEmitter to pass messages around. For example it is used to tell the reporter that the test suite has started running. When we run our Runner it emits a message 'start'. I wont explicitly mention these messages after this point - essentially at each stage of the execution an appropriate message is sent/received/acted upon.

When we call mocha.run a UI is setup. The default is the BDD (behaviour driven development) UI. This essentially defines a number of methods.. namely describe(), it(), and the various hook methods. These are what you use to write your tests.

describe

When you call describe a new suite is created. What Mocha does here (if I have understood correctly) to allow for nested suites is very very clever.

Javascript executes syncronously, line after line. Within describe, a suite is created using the suite at the front of the locally available suites array as its parent. Once a suite has been created it is added to the start of this array.

It then executes the passed in function body of the describe call. If it has nested describe calls, these now use the new suites[0] as their parent when the respective suite is created.

After the function has completed executing, the suite is removed from the suites array.

Another interesting tidbit is that suites inherit their parents context (ctx).

  var context = function() {};
  context.prototype = parentContext;
  this.ctx = new context();

This allows them to inherit timeout settings amongst other things.

it

When you call it, a new Test is created and this is added to the suite. it() is executed in the context of its suite by using call() from within describe().

The Runner has a runSuite method which is called with Mocha's initial base suite. This method runs the appropriate 'suite level' Hooks, and then runs any tests (runTests). runTests calls the beforeEach hooks, which on completion runs the test, which on completion runs the afterEach hooks, which on completion runs the next test. Once all the tests are completed the callback from the level above is executed which will recursively execute runSuite on any nested suites.

The runTests method does similar in the sense that it runs the respective hooks at the right times (beforeEach, and afterEach). afterEach is passed a callback which on completing the hook is executed. This callback runs the next test allowing for test recursion.

The running of a test involves calling the run method on the test object.

The test object inherits this method from Runnable. Runnable has the method run which sets the test as the runnable within the Context object (initially set at the very lowest level in the instantiation of Mocha).

Now.. one of the great features of Mocha is that you can have tests running asynchronous code. The way this works is simple. I initially suspected that this would be done with timers and regular polling. It is in fact a lot cleaner and simpler than that.

The execution of sequential tests as mentioned above is controlled by its previous test in that within the call Test.run(fn), fn is a callback which executes the following test. As such the Runnable run method simply does not execute this callback until it is complete (you have triggered done()) or until the test times out.

Reporting

So we have written our tests and can run them. At each step of the way we emit messages using nodes EventEmitter.

A reporter, in extremely simple terms, listens to these messages and produces an output.

In addition to that, the reporters format the output so that it is useful. Mocha comes with jsDiff so you can see visual diffs where appropriate. They also control important things like the colours of your output, and printing out cat faces..

Further reading

Mocha is quite complex under the hood, and makes use of a number of complex yet clever design patterns. This is a general more in depth overview of its internals. The next step is to read the source code - explaining all the fine details in written text would be more challenging than the actual concepts.

If anything is not clear please do let me know.


Thomas Clowes

Thomas Clowes

I am a 28 year old software engineer from the United Kingdom. During the day I build multi platform applications. In my spare time I eat food and run marathons. Sometimes I write angry tweets.