问题
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