Test simple logger functions with full code coverage

…衆ロ難τιáo~ 提交于 2021-01-29 14:33:35

问题


I'm using Chai, Sinon and Instanbul to test a NodeJS application. Here's the Logger code:

import Debug, { IDebugger } from 'debug';

export default class Logger {
  private readonly dbg: IDebugger;

  constructor(name: string) {
    this.dbg = Debug(name);
  }

  public log(str: string): void {
    this.dbg(str);
  }
}

Here's the test that I have built to start with:

import * as fs from 'fs';
import sinon from 'sinon';
import { expect } from 'chai';
import Logger from '../../src/utils/Logger';
import Debug from 'debug';

describe('Unit tests for Logger class', () => {
  afterEach(() => {
    sinon.restore();
  });

  describe('#constructor', () => {
    it('should set logger', () => {
      const setLoggerStub = sinon.stub(Logger.prototype, 'log');
      const logExample: string = 'test logger'
      const logger = new Logger(logExample);
      logger.log('test logger')

      sinon.assert.calledWithExactly(setLoggerStub, logExample);
    });
  });
});

The code coverage report is the following:

I'm not sure why the log function is not tested, how can I test it and separate the test for the constructor and the test for the log function?

I'm not sure about what to test because the only function of log is to pass a string to the debug library. What should be the approach in this scenario?


回答1:


log isn't covered because it never got called, you stubbed it out with sinon.stub(Logger.prototype, 'log').

Your current implementation is difficult to test because Logger is too coupled to Debug. Creating instances of dependencies in the constructor is generally not a good idea.

If you invert the dependency, making Logger's constructor take an IDebugger instead of the string name:

import Debug, { IDebugger } from 'debug';

export default class Logger {

  constructor(private readonly dbg: IDebugger) {}

  public log(str: string): void {
    this.dbg(str);
  }
}

then when you test Logger, you can easily inject a test double:

it("debugs the input", () => {
  const debug = sinon.stub();
  const logger = new Logger(debug);
  const message = "hello, world!";

  logger.log(message);

  sinon.assert.calledWithExactly(debug, message);
});

This means you don't need to spy on any part of Logger itself, allowing you to refactor inside that class more easily. The Logger class now only depends on the abstract IDebugger interface, not the concrete Debug implementation.

Creating a Logger instance with a Debug given the specific name can just be a function, or a static method:

export default class Logger {

  static withName(name: string) {
    return new Logger(Debug(name));
  }

  /* ... */
}


来源:https://stackoverflow.com/questions/65290175/test-simple-logger-functions-with-full-code-coverage

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!