Exception Catch 22

That one gave me a really hard time, so I have to share… I’m implementing a logging module in a Node application, that uses Winston. The Winston feature that I wanted to test is the exception logging. That proved to be surprisingly difficult, because of conflicting exception handling from Winston and from the test runner.

You’d think that Winston itself should have some similar tests, and it does, but it spawns and instruments a child process to do so. I suppose that’s a way to do it, but that would not have worked so well in my case because the environment is a little more complex to set-up, leading to code that would have been a little too convoluted and hard to understand. Also, I don’t like it, but then again, neither do I like the solution I came up with very much…

All decent testing frameworks handle exceptions, and also provide a way to verify that a given exception is being thrown by the tested code. That doesn’t help in this situation however: what I need is for Winston to handle the exception, but I want the test runner to ignore it. What Winston does in its own test code is to completely bury the throwing code into a different process, which means setting-up most of the testing environment onto that process rather than in the test itself, which is not how I'd like things to work.

A try/catch won’t help either: I’m testing uncaught exceptions. I also can’t just throw, and let the test runner catch the exception, because in that case, Winston will never see the exception. In summary, I need a way for Winston to see an exception, and for the test runner to not see it.

In order to understand the solution that I found, you need to know how Node libraries handle exceptions: there’s a special event called “uncaughtException” on the global process object that they can handle. That’s what both Winston and test runners do to deal with exceptions.

The first part of my solution is to emit the event instead of throwing a real exception:

process.emit('uncaughtException', new Error('voluntary exception'));

The second part is to temporarily remove the test runner’s handler for the event:

// Distract the test runner's exception handler while we
// trigger an exception that we don't want it to notice.
var listeners = process.listeners('uncaughtException');
var testRunnerListener = listeners.filter(function(listener) {
return listener.name === 'uncaught';
})[0];
process.removeListener('uncaughtException', testRunnerListener);
// Let Winston catch and log the exception.
process.emit('uncaughtException', new Error('voluntary exception'));
// Put the test runner's listener back in place.
process.addListener('uncaughtException', testRunnerListener);

The tricky part that I don’t like is that I had to use some knowledge I had about the test runner, i.e. the function name of its handler. This, however, works perfectly, and I can verify that my exception is getting logged.

In case you’re wondering, domains won’t make this any better, because the event handler from the test runner will still be triggered even if the throwing code runs in a domain: the event will still bubble up. The answers do, however, suggest an interesting potential solution, which is to do something async such as a process.nextTick in order to trick the domain into not bubbling the event. That, however, still doesn’t work, as the event still bubbles to the test runner.

I’m not satisfied with the technical details of the above solution, of course, so if any of you have any suggestions to make this better, or write it entirely differently, I’d love to read your thoughts.

No Comments