如何在Jest中模拟NestJS内置Logger



我有一个控制器,它通过在控制器的构造函数中注入依赖项来使用NestJS内置Logger:

constructor(private readonly logger: Logger) 

我希望能够在我的Jest测试中模拟它,看看在日志记录过程中调用了哪些方法和哪些参数。我尝试了以下语法:

providers[{
provide: Logger,
useValue: {
log: jest.fn(),
}
}]

在这种情况下,这一行:

expect(Logger).toHaveBeenCalledTimes(1);

退货:匹配器错误:接收到的值必须是模拟或间谍函数

如有任何帮助,我们将不胜感激!

在测试中,您应该使用moduleFixture.get(Logger)(或类似的东西)将记录器从DI上下文中取出,然后检查expect(logger.log).toHaveBeenCalledTimes(1)Logger本身就是一个类,而不是间谍或模拟,所以Jest不知道该怎么处理它。

有效的完整解决方案:

import { Test } from '@nestjs/testing';
let logger: Logger;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [  
{
provide: Logger,
useValue: {
log: jest.fn(),
},
},
],
}).compile();
logger = moduleRef.get<Logger>(Logger);
});

然后在稍后的测试中:

expect(logger.log).toHaveBeenCalledTimes(1);
expect(logger.log).toHaveBeenCalledWith('Your log message here')

我将把这作为我最近如何处理这件事的答案。所以下次我在谷歌上搜索并在这里结束时,我会记得

jest.mock不成功

我已经尝试过像通常从@nest/common导入Logger类时那样使用jest.mock,但它似乎会导致自身的问题。即使你试图保持原来的实现方式:

jest.mock('@nestjs/common', () => ({
...jest.requireActual('@nestjs/common'),
Logger: jest.fn(),
}))

我仍然想相信一定有办法做到这一点,但也许Nest JS的依赖系统绕过了Jest的嘲笑?

✅使用带有Nest JS依赖项注入的自定义记录器

这感觉像是不必要的提升,但它遵循了NestJS的依赖注入,并允许稍后进行扩展或覆盖。如果你已经在为NestJS编写测试了,你可能已经很熟悉了。

custom.logger.ts

import { ConsoleLogger } from '@nestjs/common'
export class CustomLogger extends ConsoleLogger {}

some-consumer.spec.ts

这种方法使用jest-mock-extended库,但您也可以执行类似@Jay McDoniel的答案的操作。

import { SomeConsumer } from './some-consumer'
import { CustomLogger } from './custom.logger'
import { Test } from '@nestjs/testing'
import { mockDeep } from 'jest-mock-extended'
describe('SomeConsumer', () => {
let someConsumer: SomeConsumer
const logger = mockDeep<CustomLogger>()
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
SomeConsumer,
{
provide: CustomLogger,
useValue: logger,
},
],
}).compile()
someConsumer = module.get(SomeConsumer)
})
it('should do something', () => {
const result = someConsumer.doSomething()
expect(result).toEqual('something returned')
})
it('should log something', () => {
someConsumer.doSomething()
expect(logger.log).toHaveBeenCalledWith('something')
})
})

some-consumer.ts

我想我会提供一个记录器被消耗的例子。

import { Injectable } from '@nestjs/common'
import { CustomLogger } from './custom-logger'
@Injectable()
export class SomeConsumer {
constructor(private readonly logger: CustomLogger) {}
public doSomething(): string {
this.logger.log('something')
return 'something returned'
}
}

✅从@nestjs/common导入Logger的二次尝试

我在其他地方看到有人提到你可以在模块中设置记录器,所以我尝试了一下,它似乎也能正常工作

some-consumer-imported.ts

import { Injectable, Logger } from '@nestjs/common'
@Injectable()
export class SomeConsumerImported {
private logger = new Logger(SomeConsumerImported.name)
public doSomething(): string {
this.logger.log('something logged')
return 'something returned'
}
}

some-consumer-imported.spec.ts

import { SomeConsumerImported } from './some-consumer-imported'
import { Logger } from '@nestjs/common'
import { Test } from '@nestjs/testing'
import { mockDeep } from 'jest-mock-extended'
describe('SomeConsumerImported', () => {
let someConsumerImported: SomeConsumerImported
const logger = mockDeep<Logger>()
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [SomeConsumerImported],
}).compile()
module.useLogger(logger)
someConsumerImported = module.get(SomeConsumerImported)
})
it('should do something', () => {
const result = someConsumerImported.doSomething()
expect(result).toEqual('something returned')
})
it('should log something', () => {
someConsumerImported.doSomething()
expect(logger.log).toHaveBeenCalledWith('something logged', SomeConsumerImported.name)
})
})

您可以直接在Logger类上使用jest.SpyOn

jest.spyOn(Logger, 'log');
expect(Logger.Log).toHaveBeenCalledTimes('error', 'SmsService.sendSMS');

最新更新