javascriptnode.jsunit-testingmocha.jsbunyan

How would i write a test for something which uses stdout?


I'm trying to improve test coverage on this file

'use strict'

/**
 * Returns the logging options we're using
 *
 * @param   {String} name Name of the logger. Should be service name. e.g. ciitizen-file-service
 * @returns {Object}      The logging options we're using
 * @private
 */
function getLoggerOptions(name) {
  return {
    name: name,
    streams: [
      {
        level: 'info',
        stream: stdOutStream()
      }, {
        level: 'error',
        stream: stdErrStream()
      }
      , {
        level: 'debug',
        stream: stdOutStream()
      },
      {
        level: 'warn',
        stream: stdOutStream()
      },
      {
        level: 'fatal',
        stream: stdErrStream()
      }
    ]
  }
}

/**
 * Creates a stream that writes to stdout
 *
 * @param   {Object} info The log info
 * @returns {Object}      An object which logs to stdout
 * @private
 */
function stdOutStream() {
  return {
    write: log => {
      // Change log level number to name and write it out
      log.level = bunyan.nameFromLevel[log.level]
      process.stdout.write(JSON.stringify(log) + '\n')
    }
  }
}

/**
 * Creates a stream that writes to stderr
 *
 * @param   {Object} info The log info
 * @returns {Object}      An object which logs to stderr
 * @private
 */
function stdErrStream() {
  return {
    write: log => {
      // Change log level number to name and write it out
      log.level = bunyan.nameFromLevel[log.level]
      process.stderr.write(JSON.stringify(log) + '\n')
    }
  }
}

module.exports = { getLoggerOptions }

I currently have this test

'use strict'

const expect = require('chai').expect
const { getLoggerOptions } = require('../src/helpers')

describe.only('#getLoggerOptions', () => {
  it('should return the logger options', () => {
    expect(getLoggerOptions('test')).to.be.an('object')
  })
})

Which produces this gap in unit test coverage enter image description here

How can i write a test to cover this code?


Solution

  • You are only asserting that getLoggerOptions(...) should returns an object, but you are not accessing the object.

    For instance, apart from the posted test, try adding a test like:

    describe.only('#probeLoggerOptions', () => {
      it('should do expected logging stuff', () => {
        const {name, streams} = getLoggerOptions('test');
        for (let stream of streams) {
          console.log(`getting logopt for level ${stream.level}`);
          expect(stream.stream.write(log), /* ... what to expect */);
        }
      });
    });
    

    Now the test is calling the write function in each object.

    EXTRA: If you own the code, the best way is to refactor it to be more testable. For instance, instead of writing to high-level process streams directly, you might create a WritableStream, write to it, then pipe to the desired process stream.

    function configureStream(writable = process.stdout, data = null, msg = 'done') {
      const stream = fs.createReadStream('/tmp/log');
      stream.read(data);
      stream.pipe(writable);
    

    }

    function stdOutStream() {
      return {
        write: log => {
          // Change log level number to name and write it out
          log.level = bunyan.nameFromLevel[log.level];
          configureStream(data = JSON.stringify(log) + '\n');
        }
      }
    }
    

    Apart from being able to test configureStream separately, you could also abstract the meat of each logging process to one function that reduce the test surface. Now you could also hook into stream's events like 'pipe' or 'finish' to check the content being logged.