You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用ts-mockito正确模拟带命名的NestJS Logger

NestJS命名Logger单元测试Mock方案

问题场景

项目技术栈:

  • 后端框架:NestJS
  • 单元测试工具:mocha、chai、ts-mockito

核心异常:无参实例化的NestJS Logger 可以正常完成mock断言,传入名称参数的命名Logger实例无法被正确捕获调用,断言失败。

可正常运行的无参Logger测试代码

describe('basic test', () => {
    it('works', async () => {
        const mockLogger = mock<LoggerService>();
        const moduleRef = await Test.createTestingModule({
            providers: [
                {
                    provide: TestClass,
                    useValue: new TestClass(),
                },
            ],
        })
            .setLogger(instance(mockLogger))
            .compile();

        const unit = moduleRef.get(TestClass);

        unit.log();

        verify(mockLogger.error(anything())).once();
    });
});

class TestClass {
    readonly logger: Logger;
    constructor() {
        this.logger = new Logger();
    }

    public log() {
        this.logger.error(new Error());
    }
}

触发异常的命名Logger代码

仅修改TestClass中Logger的实例化方式,传入上下文名称参数:

class TestClass {
    readonly logger: Logger;
    constructor() {
        this.logger = new Logger('name');
    }

    public log() {
        this.logger.error(new Error());
    }
}

测试抛出错误信息:

// Expected "error(anything())" to be called 1 time(s). But has been called 0 time(s).

即校验mockLogger.error方法被调用1次的断言失败,实际该方法未被调用。

问题根因

setLogger()方法仅能设置测试模块自身的日志实例,供无参实例化的Logger直接复用;传入上下文名称的命名Logger在实例化时,会从NestJS全局静态Logger配置中拉取默认日志实现,不会读取测试模块通过setLogger()传入的mock实例,因此mock对象上的日志方法不会被触发。

修复方案

在编译测试模块前,调用NestJS Logger自带的静态方法overrideLogger()全局替换默认日志实现,让所有实例(无论是否带名称参数)的日志调用都转发到mock对象上即可。

修正后的测试代码:

import { Logger, LoggerService } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { anything, instance, mock, verify, reset } from 'ts-mockito';

describe('named logger test', () => {
    let mockLogger: LoggerService;

    // 每个用例执行前重置mock,避免用例间污染
    beforeEach(() => {
        mockLogger = mock<LoggerService>();
        // 全局覆盖NestJS默认Logger实现,覆盖所有带/不带名称的Logger实例
        Logger.overrideLogger(instance(mockLogger));
    });

    // 用例执行后还原默认Logger,避免影响其他测试文件
    afterEach(() => {
        reset(mockLogger);
        Logger.overrideLogger(undefined);
    });

    it('works for named logger', async () => {
        const moduleRef = await Test.createTestingModule({
            providers: [
                {
                    provide: TestClass,
                    useValue: new TestClass(),
                },
            ],
        })
            .setLogger(instance(mockLogger))
            .compile();

        const unit = moduleRef.get(TestClass);
        unit.log();

        verify(mockLogger.error(anything())).once(); // 断言正常通过
    });
});

class TestClass {
    readonly logger: Logger;
    constructor() {
        this.logger = new Logger('name');
    }

    public log() {
        this.logger.error(new Error());
    }
}

可选替代方案

如果不想修改全局Logger静态配置,也可以直接在测试中给被测类的logger属性赋值mock实例,跳过内部Logger的实例化逻辑:

const unit = new TestClass();
// 直接替换类内部的logger实例为mock对象
(unit as any).logger = instance(mockLogger);

该方案不需要依赖NestJS测试模块的日志配置,但需要注意访问类内部属性的权限问题。


内容的提问来源于stack exchange,提问作者qujck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:21:34