Imagine a Mocha test file like this
1 | describe('my tests', function () { |
The single test named "works" is synchronous; it passes
1 | $ cat package.json |
What if the test is asynchronous? We need to either return a promise or accept done test
callback parameter. Let us use done - it is simpler to call from a setTimeout
1 | describe('my tests', function () { |
1 | $ npm t |
Great. What if the test takes 3 seconds?
1 | it('passes after 3000ms', function (done) { |
1 | $ npm t |
Mocha uses 2 second test limit by default. Let us increase the timeout to 3.5 seconds in that one test.
1 | it('passes after 3000ms', function (done) { |
1 | ✓ passes after 3000ms (3002ms) |
Great, the test is passing. Does Mocha pass anything else into the test callback function?
We can check by printing arguments
1 | it('passes after 3000ms', function (done) { |
1 | test arguments { '0': [Function] } |
Seems, the test callback only gets the done parameter and nothing else. Are there any other
methods the test callback can call on its context besides this.timeout? Let us print the
this variable inside the test.
1 | it('passes after 3000ms', function (done) { |
1 | TypeError: Converting circular structure to JSON |
Hmm, not good. If we try printing using console.log('this %j', this) we are not getting
much more information, but at least we are not crashing
1 | test arguments { '0': [Function] } |
Ok, let us print the keys of the object
1 | // inside the test |
1 | this [ '_runnable', 'test' ] |
We are getting something! The test property is especially interesting. It has the name,
the test callback and other properties describing the current test.
1 | it('passes after 3000ms', function (done) { |
1 | test arguments { '0': [Function] } |
Via this.test we have access to the test's code (this.test.body), the test title, its file,
its parent suite of tests, etc. This comes in very handy when extending Mocha with
snap-shot testing for example.
Test closures
But what happens if we get tired of writing "long" callback functions and instead use arrow functions?
1 | it('passes after 3000ms', (done) => { |
1 | test arguments {} |
Everything breaks! Why is this still an object, but this.timeout has no effect, and
the property this.test is undefined?
When you use a "normal" callback function, Mocha creates a Test instance and binds it as this
when calling your callback. It could be something like this behind the scene
1 | const allTests = [] |
By using test.cb.call(test, ...) the test runner sets this inside the test callback function
to the full "Mocha.Test" instance. What happens when you use arrow function as a test callback?
The arrow functions bind the this context to whatever was outside their closure. If you are
unsure what JavaScript closures are, read this blog post. In our
example, inside the callback this will be whatever it was outside the callback's source code
in our spec file, which is "describe" callback function!

The function surrounding our test arrow callback as written in the spec.js file is
the describe callback "full" function. Mocha test runner creates a special context when
executing each describe callback, thus the spec, instead of proper Mocha.Test instance
gets something like Mocha.Describe instance! This leads to the confusion and produces the
dummy this.timeout method that does nothing.
Even worse, what happens if the describe function uses arrow function as callback?
1 | describe('my tests', () => { |
1 | $ npm t |
That is unexpected. The this.timeout() call used this which due to arrow function callback
points at this inside the describe callback; which itself points outside because it is a
callback function. When you point outside the outer function what do you get? In JavaScript
this differs. If you are inside a proper function, the outside context would be
a global object (Node) or window object (browser). So if we wrap our describe in a
dummy function foo, we would get this === global inside each test.
1 | function foo () { |
1 | test arguments {} |
My general advice when dealing with scope madness like this (no pun intended) is to use
the strict mode to prevent default context pointing at global
1 |
|
1 | test arguments {} |
But: if we do not use our outside foo function, using strict mode has no effect!
1 |
|
1 | test arguments { '0': {}, |
I think I can speak for everyone when I say "WTF".
What is this empty context {} object we are getting? What is this huge arguments object
we are seeing in the arrow function? Why does everyone have to be so complicated?
Well, it is still due to the JavaScript closure scope rules.
First, about the weird arguments object. When you use the arrow function
you "lose" your immediate arguments and instead your arguments points at the first full closure
function's arguments object!
1 | // index.js |
Notice how we are passing arguments to foo and bar. What are the arguments inside
bar arrow function?
1 | $ node index.js |
They are arguments of foo()! Ok, a little crazy, but I guess if this points at the
outside full function's closure, arguments might as well. So what are the magical 5
arguments our spec callback function got? Where are they coming from? Well, this is from
Node's require function
(for full code example see Hacking Node require). Every time
a JS file is loaded by Node, it does the following
1 | const source = fs.readFileSync(filename, 'utf8') |
The require wraps the spec.js in a full function, passing 5 parameters - that is
where "magical" variables __filename and __describe are coming from! If we do not have
a proper function inside out tests of our own, the arrow functions "find" the outside
function from require and use its context (bypassing use strict command) and even
getting its arguments object.
What a mess. And all because the Mocha test runner uses this to let the test code
set its time limit.
Final thoughts
A couple of points to finish this discussion.
Whenever I need a custom timeout in one of my test callbacks, I make sure to use "proper" callback function.
1
2
3
4
5describe('my tests', () => {
it('passes after 3000ms', function (done) {
this.timeout(3500)
})
})Other test frameworks like Tape and Ava avoid using
thisand pass you and explicit argument. Simple and safe, see my test framework recommendationsthiskeyword in JavaScript will burn you one day. Then it will burn you again and again and again. If Dante Alighieri were alive today, he would put writing object-oriented JavaScript among one of the first levels of Hell for sure.

Please avoid the eternal suffering by using functional programming with its emphasis of pure functions.