我正在尝试模拟 winston.Logger 实例,该实例封装在使用NestJS创建的服务类中。我在下面包含了我的代码。
我无法从服务类中触发模拟的记录器实例。谁能解释我要去哪里错了?
import * as winston from 'winston';
import { loggerOptions } from '../logger/logger.config';
import { LoggingService } from '../logger/logger.service';
const logger: winston.Logger = winston.createLogger(loggerOptions);
// trying to mock createLogger to return a specific logger instance
const winstonMock = jest.mock('winston', () => (
{
format: {
colorize: jest.fn(),
combine: jest.fn(),
label: jest.fn(),
timestamp: jest.fn(),
printf: jest.fn()
},
createLogger: jest.fn().mockReturnValue(logger),
transports: {
Console: jest.fn()
}
})
);
describe("-- Logging Service --", () => {
let loggerMock: winston.Logger;
test('testing logger log function called...', () => {
const mockCreateLogger = jest.spyOn(winston, 'createLogger');
const loggingService: LoggingService = LoggingService.Instance;
loggerMock = mockCreateLogger.mock.instances[0];
expect(loggingService).toBeInstanceOf(LoggingService)
expect(loggingService).toBeDefined();
expect(mockCreateLogger).toHaveBeenCalled()
// spy on the winston.Logger instance within this test and check
// that it is called - this is working from within the test method
const logDebugMock = jest.spyOn(loggerMock, 'log');
loggerMock.log('debug','test log debug');
expect(logDebugMock).toHaveBeenCalled();
// now try and invoke the logger instance indirectly through the service class
// check that loggerMock is called a second time - this fails, only called once
// from the preceding lines in this test
loggingService.debug('debug message');
expect(logDebugMock).toHaveBeenCalledTimes(2);
});
...
LoggingService调试方法代码
public debug(message: string) {
this.logger.log(
{
level: types.LogLevel.DEBUG,
message: message,
meta: {
context: this.contextName
}
}
);
}
更新:2019年3月9日
将我的nestjs LoggingService重构为在构造函数中依赖注入winston logger实例,以方便进行单元测试。这使我能够在Winston记录器的log方法上使用 jest.spyOn 并检查是否已在服务实例中调用它:
// create winstonLoggerInstance here, e.g. in beforeEach()....
const winstonLoggerMock = jest.spyOn(winstonLoggerInstance, 'log');
serviceInstance.debug('debug sent from test');
expect(winstonLoggerMock).toHaveBeenCalled();
答案 0 :(得分:1)
我最近遇到了同样的问题,并通过在自定义记录器中使用jest.spyOn来解决了这个问题。
注意:您不必对winston.createLogger()进行单元测试。 Winston模块具有涵盖该功能的自己的单元测试。
一些记录错误的函数(例如./controller.ts
)
import defaultLogger from '../config/winston';
export const testFunction = async () => {
try {
throw new Error('This error should be logged');
} catch (err) {
defaultLogger.error(err);
return;
}
};
该功能的测试文件(即`./tests/controller.test.ts):
import { Logger } from 'winston';
import defaultLogger from '../../config/winston';
import testFunction from '../../controller.ts';
const loggerSpy = jest.spyOn(defaultLogger, 'error').mockReturnValue(({} as unknown) as Logger);
test('Logger should have logged', async (done) => {
await testFunction();
expect(loggerSpy).toHaveBeenCalledTimes(1);
});
答案 1 :(得分:0)
我已经测试了您的代码,并且jest.mock的使用似乎存在多个问题。
为了正确模拟模块,必须在导入之前首先模拟它。这是一种内部机制(玩笑如何模拟模块),您必须遵循此规则。
const logger = {
debug: jest.fn(),
log: jest.fn()
};
// IMPORTANT First mock winston
jest.mock("winston", () => ({
format: {
colorize: jest.fn(),
combine: jest.fn(),
label: jest.fn(),
timestamp: jest.fn(),
printf: jest.fn()
},
createLogger: jest.fn().mockReturnValue(logger),
transports: {
Console: jest.fn()
}
}));
// IMPORTANT import the mock after
import * as winston from "winston";
// IMPORTANT import your service (which imports winston as well)
import { LoggingService } from "../logger/logger.service";
如您所见,您不能将Winston实例用作模拟的返回值,但也不必担心,也要模拟该实例。 (您也可以在前面的代码示例中看到它)
const logger = {
debug: jest.fn(),
log: jest.fn()
};
最后,您无需监视曾经嘲弄过的内容,只需直接询问模拟内容即可。
完整的代码在这里:
const logger = {
debug: jest.fn(),
log: jest.fn()
};
// trying to mock createLogger to return a specific logger instance
jest.mock("winston", () => ({
format: {
colorize: jest.fn(),
combine: jest.fn(),
label: jest.fn(),
timestamp: jest.fn(),
printf: jest.fn()
},
createLogger: jest.fn().mockReturnValue(logger),
transports: {
Console: jest.fn()
}
}));
import * as winston from "winston";
import { LoggingService } from "./logger.service";
describe("-- Logging Service --", () => {
let loggerMock: winston.Logger;
test("testing logger log function called...", () => {
const mockCreateLogger = jest.spyOn(winston, "createLogger");
const loggingService: LoggingService = LoggingService.Instance;
loggerMock = mockCreateLogger.mock.instances[0];
expect(loggingService).toBeInstanceOf(LoggingService);
expect(loggingService).toBeDefined();
expect(mockCreateLogger).toHaveBeenCalled();
// spy on the winston.Logger instance within this test and check
// that it is called - this is working from within the test method
logger.log("debug", "test log debug");
expect(logger.log).toHaveBeenCalled();
// now try and invoke the logger instance indirectly through the service class
// check that loggerMock is called a second time - this fails, only called once
// from the preceding lines in this test
loggingService.debug("debug message");
expect(logger.debug).toHaveBeenCalledTimes(1); // <- here
});
});
我将最终断言更改为一个,因为我在测试中调用了log
,在LoggingService中调用了debug
。
这是我使用的记录器服务:
import * as winston from "winston";
export class LoggingService {
logger: winston.Logger;
static get Instance() {
return new LoggingService();
}
constructor() {
this.logger = winston.createLogger();
}
debug(message: string) {
this.logger.debug(message);
}
}
玩得开心!