Testing the Requirements of Failure in node applications

tl;dr

Use capture-stdout to easily test logging of errors (or any other expected output to stdout) in node applications.

Requirements of Failure

How are you writing your code to handle failures?

When I had some experience (and bruises) supporting code in production, I begin to realize the importance of exception and error handling separate from the exhortation of my leads.   There’s been a lot written on writing defensive code that handles “things that go wrong”.    Often the way these errors are  handled (pun intended) is through logging and log monitoring.   For example, when trying to load records from the database, we will typically catch any errors, log them, and then present the user with something a bit more friendly than a call stack.   The logging of that error has just become a requirement.   While we might not want to log when we get a key collision on the creation of a record, I do want to know when, where, and how the database calls are failing, and I probably want to know sooner rather than later.

When logging exceptions, it’s probably a requirement.

Often, logging these failures have made them a requirement.   If I’ve got a notification tied to a number of ‘error’ level log messages inside of a time period, it’s DEFINITELY a requirement.   Basically anytime you think you’ll want to take a closer look at the application status because of some log messages, they are most likely ‘Requirements of Failure’.

Are you testing for ‘Requirements of Failure’?

Testing logging then becomes a little more difficult.   Yes, we can stub out the logger and handle it that way.   But at the integration level, I want to know that the expected end log message is making it to the expected destination so I know I won’t walk into work on a Monday morning, only to find that a component failure that wasn’t logged properly brought some portion of my application down.  Its simple enough to add a case to the unit and/or integration tests to force a failure and make sure the tests return the expected value.   Here’s one solution to testing your logging output in a node.js application.

capture-stdout

There are some other projects out there, but most of them have significant overhead or they only copy the output.   There’s nothing uglier than seeing exceptions sprinkled throughout your test results.   So I wrote a very simple ES6 class (hey, its holding data, set aside your prototypical fears and biases and just run with it), to capture stdout data and make that data available.   Take a look at the github repository and the module package over at npm.   Here’s the class (minus the documentation cruft):

class CaptureStdout {
  constructor() {
    this._capturedText = [];
    this._orig_stdout_write = null;
  }
  startCapture() {
    this._orig_stdout_write = process.stdout.write;
    process.stdout.write = this._writeCapture.bind(this);
  }
  stopCapture() {
    if (this._orig_stdout_write) {
      process.stdout.write = this._orig_stdout_write;
    }
  }
  _writeCapture(string) {
    this._capturedText.push(string.replace(/\n/g, ''));
  }
  getCapturedText() {
    return this._capturedText;
  }
  clearCaptureText() {
    this._capturedText = [];
  }
}

That’s it. Easy Peasy, no dependencies, just plug it in and run with it.

Installation

npm install capture-stdout

Example Usage

  it('should error and log an error message if the persistence call throws an error', async () => {
    const captureStdout = new CaptureStdout();
    const msg = 'some error text here';

    // stub out the persistence call
    const findAll = sandbox.stub(orm.books, 'findAll');
    // stubbed persistence call is a promise, fail as one.
    findAll.rejects(Error(msg));

    captureStdout.startCapture();

    controller.getAll(req, res, next);
    await res;
    await next;

    captureStdout.stopCapture();
    const arrJson = captureStdout.getCapturedText().map(JSON.parse);
    captureStdout.clearCaptureText();

    expect(arrJson).has.lengthOf(1);
    expect(arrJson[0]).has.property('msg').contains(msg);
    expect(arrJson[0]).has.property('level').which.equals(50);
  });

Wrapping up

Using capture-stdout has already helped me out on some of the projects I’ve got underway.   Hopefully you’ll find it useful, and if nothing else, maybe this will make you think about how you’re testing your ‘Requirements of Failure’.

If you have any questions or issues, drop me a line.   🙂   Ciao!

Leave a Reply

Your email address will not be published. Required fields are marked *