测试具有完整代码覆盖率的简单记录器功能

Test simple logger functions with full code coverage

我正在使用 Chai、Sinon 和 Instanbul 来测试 NodeJS 应用程序。这是 Logger 代码:

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);
  }
}

这是我构建的开始测试:

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);
    });
  });
});

代码覆盖率报告如下:

我不知道为什么 log 函数没有被测试,我该如何测试它并将构造函数的测试和 log 函数的测试分开?

我不确定要测试什么,因为 log 的唯一功能是将字符串传递给 debug 库。在这种情况下应该采取什么方法?

log 未包含在内,因为它 从未被调用过 ,您用 sinon.stub(Logger.prototype, 'log').

将其删除

您当前的实现很难测试,因为 LoggerDebug 的耦合度太高。在构造函数中创建依赖实例通常不是一个好主意。

如果反转依赖关系,使 Logger 的构造函数采用 IDebugger 而不是 string 名称:

import Debug, { IDebugger } from 'debug';

export default class Logger {

  constructor(private readonly dbg: IDebugger) {}

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

然后当你测试Logger时,你可以很容易地注入一个测试替身:

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);
});

这意味着您不需要监视 Logger 本身的任何部分,让您可以更轻松地重构 class 内部。 Logger class 现在只依赖于抽象的 IDebugger 接口,而不是具体的 Debug 实现。

创建具有 DebugLogger 实例给定特定名称可以只是一个函数,或一个静态方法:

export default class Logger {

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

  /* ... */
}