如何使用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
相关产品推荐
相关产品推荐

