19

I would like to test that the following function performs as expected:

function throwNextTick(error) {
    process.nextTick(function () {
        throw error;
    });
}

Here is my attempt:

describe("throwNextTick", function () {
    it("works as expected", function (next) {
        var error = new Error("boo!");
        var recordedError = null;
        process.once("uncaughtException", function (error) {
            recordedError = error;
        });

        throwNextTick(error);

        process.nextTick(function () {
            recordedError.should.be(error);
            next();
        });
    });
});

But mocha seems to want to keep any errors to itself, and fail my test when it gets them:

C:\Users\ddenicola\Programming (Synced)\pubit>mocha test/basicTest.js

  throwNextTick
    0) works as expected

  ? 1 of 1 tests failed:

  1) throwNextTick works as expected:
     Error: boo!
      at Test.fn (C:\Users\ddenicola\Programming (Synced)\pubit\test\basicTest.js:11:21)
      at Test.run (C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:144:15)
      at Runner.runTest (C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:271:10)
      at C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:315:12
      at next (C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:199:14)
      at C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:208:7
      at next (C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:157:23)
      at Array.0 (C:\Users\ddenicola\AppData\Roaming\npm\node_modules\mocha\lib\runner.js:176:5)
      at EventEmitter._tickCallback (node.js:192:40)

Any ideas?

4 Answers 4

30
+100

Update: Courtesy of casey-foster in a comment below:

As of node v6.0.0 you can use process.prependOnceListener('uncaughtException', ...) to do this much more succinctly.


Old answer:

The secret lies in process.listeners('uncaughtException'):

http://nodejs.org/docs/latest/api/events.html#emitter.listeners

Simply remove the mocha listener, add your own, then reattach the mocha listener.

See below:

var assert = require('assert')

function throwNextTick(error) {
    process.nextTick(function () {
        throw error
    })
}


describe("throwNextTick", function () {
    it("works as expected", function (next) {
        var error = new Error("boo!")
        var recordedError = null
        var originalException = process.listeners('uncaughtException').pop()
        //Needed in node 0.10.5+
        process.removeListener('uncaughtException', originalException);
        process.once("uncaughtException", function (error) {
            recordedError = error
        })
        throwNextTick(error);
        process.nextTick(function () {
            process.listeners('uncaughtException').push(originalException)
            assert.equal(recordedError, error)
            next()
        })
    })
})
Sign up to request clarification or add additional context in comments.

6 Comments

This did the trick! Only one edit I'll make to your answer: you need to restore the original listener before doing the assert, since the assert throws an error.
This really should be in Mocha's documentation! It took me an hour of googling to find this answer.. Mocha's undocumented capturing of errors (and failing tests), combined with the fact that no error message is shown if you are not throwing a standard Error object (per Crockford's advice), makes it really hard to figure out why the test is failing.
It seems that in the latest version of node (v0.10.5 at the time of this comment) process.listeners(eventName) returns a copy of the listeners array, and so calling pop() on it will not actually remove the listener from the emitter. You will need to add the following line after the pop: process.removeListener('uncaughtException', originalException);
Since process.listeners(eventName) returns a copy, at the end, instead of push, you'll want to add it back.
As of node v6.0.0 you can use process.prependOnceListener('uncaughtException', ...) to do this much more succinctly.
|
1

If your async code is executed within a domain - and that is often the case - you need to change the error listener on the domain instead of the process.

For that you can use:

it('should produce an unhandled exception', function (done) {

    // Remove Mocha's error listener
    var originalErrorListeners = process.domain.listeners('error');
    process.domain.removeAllListeners('error');

    // Add your own error listener to check for unhandled exceptions
    process.domain.on('error', function () {

        // Add the original error listeners again
        process.domain.removeAllListeners('error');
        for ( var i = 0; i < originalErrorListeners.length; i+=1 ) {
            process.domain.on('error', originalErrorListeners[i]);
        }

        // For the sake of simplicity we are done after catching the unhandled exception
        done();

    });

    // This would be your async application code you expect to throw an exception
    setTimeout(function () {
        throw new Error();
    });

});

Comments

0

as an option indicate in the test mocha.options.allowUncaught=true Errors will not be caught in mocha in try{} catch(){} Make sure the mocha object exists in your test.

But in this case, the rest of the tests will break if the error is not caught within mocha. Disabling try{} catch(){} in mocha will only be useful when debugging.

Comments

-1

Base on timoxley & Casey Foster, in node v6++

const assert = require('assert')

describe('throwNextTick', function() {
    it('works as expected', function(next) {

        function cb(err) {
            assert.equal(err instanceof Error, true)
            next()
        }

        function test(){
            process.nextTick(() => {
                throw new Error('err')
            })
        }

        process.prependOnceListener('uncaughtException', cb)
        test()

    })
})

1 Comment

you could always run mocha with --require babel-core/register to have the import/export and add the .babelrc with {"presets": ["es2015"]}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.