How to unit test console output with mocha on nodejs?

54,031

Solution 1

I prefer mocha-sinon over "plain" sinon because it integrates nicely with Mocha.

Example:

var expect = require('chai').expect;
require('mocha-sinon');

// Function to test, can also be in another file and as long as it's
// being called through some public interface it should be testable.
// If it's not in any way exposed/exported, testing will be problematic.
function privateFunction (time) {
  if (time < 12) { console.log('Good morning'); }
  if (time >= 12 && time <19) { console.log('Good afternoon'); }
  else { console.log('Good night!'); }
}

describe('privateFunction()', function() {

  beforeEach(function() {
    this.sinon.stub(console, 'log');
  });

  it('should log "Good morning" for hours < 12', function() {
    privateFunction(5);
    expect( console.log.calledOnce ).to.be.true;
    expect( console.log.calledWith('Good morning') ).to.be.true;
  });

  it('should log "Good afternoon" for hours >= 12 and < 19', function() {
    privateFunction(15);
    expect( console.log.calledOnce ).to.be.true;
    expect( console.log.calledWith('Good afternoon') ).to.be.true;
  });

  it('should log "Good night!" for hours >= 19', function() {
    privateFunction(20);
    expect( console.log.calledOnce ).to.be.true;
    expect( console.log.calledWith('Good night!') ).to.be.true;
  });

});

One potential issue: some Mocha reporters use console.log as well, so the tests that stub it may not yield any output.

There's a workaround, but it's not ideal either because it will intersperse Mocha output with the output from privateFunction(). If that's not a problem, replace beforeEach() with this:

beforeEach(function() {
  var log = console.log;
  this.sinon.stub(console, 'log', function() {
    return log.apply(log, arguments);
  });
});

Solution 2

ignoring the fact that it's a private function, i would take a couple of steps; refactor my code for better separation of concerns, and utilise this separation with test doubles.

  • take all the side effects outside to their own modules (the side effect here is writing to the console):

    out.js

    function log (message) {
      console.log(message);
    };
    
    module.exports = {log};
    

    app.js

    const {log} = require('out');
    
    function greeter (time) {
      if (time < 12) {
        log('Good morning');
      }
      if (time >= 12 && time < 19) {
        log('Good afternoon');
      } else {
        log('Good night!');
      }
    };
    
    module.exports = {greeter};
    
  • use some module proxy/spy, like proxyquire to replace the whole out writer when testing:

    app.spec.js

    describe('output writers', function(){
    
      const fakeOut = {
        log: sinon.spy(),
      };
    
      const app = proxyquire('./app', {
        'out': fakeOut
      });
    
      it('should log to the fake out', function(){
        app.greeter(15);
        assert(fakeOut.log.calledOnce);
      });
    });
    

Solution 3

If your purpose is solely to test console output, I would suggest instead of stubs / spies etc on method calls, to use something like:

Share:
54,031
Kemel Zaidan
Author by

Kemel Zaidan

Updated on July 29, 2022

Comments

  • Kemel Zaidan
    Kemel Zaidan almost 2 years

    Take into account the following example Javascript code below:

    function privateFunction (time) {
      if (time < 12) { console.log('Good morning'); }
      if (time >= 12 && time <19) { console.log('Good afternoon'); }
      else { console.log('Good night!'); }
    };
    

    How should I unit test that on nodejs using mocha (and possibly sinonjs), noticing that this is a private function called inside a module? I need to pass in the argument and check if the function is logging the right thing to the console.

    Can I do the same with console.warn and console.error?

  • TMG
    TMG over 7 years
    If you use spy instead of stub then the workaround is not needed.
  • robertklep
    robertklep over 7 years
    @TMG I'm actually starting to wonder if the workaround won't cause issues with calls to console.log() being made by reporters interfering with the call count of the stub/spy. Probably not, because I would think reporters only report after the test has run. In any case, using a spy is indeed a better solution.