如何使用Jest模拟封装在服务类中的winston日志记录器示例

j2qf4p5b  于 2023-02-10  发布在  Jest
关注(0)|答案(3)|浏览(142)

我尝试模拟一个 * 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);
    });

   ...

日志服务调试方法代码

public debug(message: string) {
        this.logger.log(
            {
                level: types.LogLevel.DEBUG,
                message: message,
                meta: {
                    context: this.contextName
                }
            }
        );
    }

更新日期:2019年3月9日

重构了我的nestjs LoggingService,以便在构造函数中依赖注入winston日志记录器示例,以便于单元测试。这使我能够在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();
wmomyfyw

wmomyfyw1#

我已经测试了您的代码,似乎在使用jest.mock时存在多个问题。
为了正确地模拟一个模块,你必须在导入它之前先模拟它。这是一个内部机制(jest如何模拟模块),你必须遵守这个规则。

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示例作为mock的返回值,但是不用担心,也可以mock该示例(您在前面的代码示例中也可以看到)。

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

我将最后一个Assert更改为一个,因为我在测试中调用了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);
  }
}

好好玩!

x7rlezfr

x7rlezfr2#

我最近遇到了同样的问题,并通过使用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);
});
o7jaxewo

o7jaxewo3#

在所选答案的基础上,我还要补充一点,你不需要模拟整个Winston对象,你可以模拟某个部分,如下所示:

jest.mock("winston", () => {
    const winston = jest.requireActual("winston");
    winston.transports.Console.prototype.log = jest.fn();
    return winston;
});

这样的话,你就可以专注于嘲笑这一部分,而其他部分则完好无损。

相关问题