How to stub Winston logger with sinon

Viewed 1825

I have a logger which utilizes the Winston NodeJs package. The logger does additional logic, and I would like to have unit tests to ensure that the proper data is being passed to Winston. However, since I have set up external transports (such as Firehose) I do not need those to be invoked.

I am not passing in Winston as a dependency through the constructor, but I have tried stubbing the createLogger method, the log method, and Winston as a whole as I normally would when stubbing a dependency.

The createStubbedInstance method does not work with Winston (or, I have not been able to get it to work) due to the fact that Winston is not exported as a class, but as a namespace.

import { Logger, ILoggerConfig } from './src';
import * as winston from 'winston'
describe('Logger', () => {
  let loggerConfig: ILoggerConfig;
  let sandbox: sinon.SinonSandbox;
  beforeEach(() => {
    sandbox = sinon.createSandbox();

    loggerConfig = {
      correlationId: faker.random.uuid(),
      action: 'GET',
    };
    sandbox = sinon.createSandbox();
    winstonStub = sandbox.stub(winston);
    winstonStub.createLogger.resolves();
    winstonStub.log.resolves();
    ...
  });

  it('should log with INFO log level', () => {
    const logger = new Logger(loggerConfig);
    logger.info('Hello there!');

    sinon.assert.calledOnce(winstonStub.log);
    sinon.assert.calledWith(winsonStub.log, sinon.match.has("level", 'info'))
  });
import { Logger, ILoggerConfig } from './src';
import * as winston from 'winston'
describe('Logger', () => {
  let loggerConfig: ILoggerConfig;
  let sandbox: sinon.SinonSandbox;
  beforeEach(() => {
    sandbox = sinon.createSandbox();

    loggerConfig = {
      correlationId: faker.random.uuid(),
      action: 'GET',
    };
    sandbox = sinon.createSandbox();
    winstonStub = sandbox.stub(winston, 'createLogger').resolves({ log: sanbox.stub() });
    ...
  });

  it('should log with INFO log level', () => {
    const logger = new Logger(loggerConfig);
    logger.info('Hello there!');

    sinon.assert.calledOnce(winstonStub);
  });

I would expect to be able to assert that the stub would be called a certain number of times. However, the stub always has a call count of 0, and I get an error indicating that Winston cannot post to Firehose due to permission issues. I also have the Console transport set up, and still see logs in the console when I should not.

1 Answers

I couldn't use "esModuleInterop": true with import winston from 'winston' due to strict settings of the project causing a ripple effect of compilation issues.

Luckily I found a clean way to mock what I needed when mocking log directly failed.

import * as tape from "tape";
import * as sinon from "sinon";
import * as winston from "winston";

tape('Stubbing winston', (test) => {
  test.test('Or at least part of it', (test) => {

    const logSpy = sinon.spy()

    sinon
      .stub(winston, "createLogger")
      .callsFake(() => ({
        log: logSpy,
      } as unknown as Logger));

    const logger = new MyLoggerThatUsesWinston()
    logger.log('Only lost');
    logger.log('four hours');
    logger.log('on this');

    test.equal(logSpy.callCount, 3, "")
    test.end();
  }
  test.end();
}
Related