如何开玩笑模拟nestjs导入?
Posted
技术标签:
【中文标题】如何开玩笑模拟nestjs导入?【英文标题】:How to jest mock nestjs imports? 【发布时间】:2020-05-27 20:18:18 【问题描述】:我想为我的 nestjs 'Course' 存储库服务(依赖于 Mongoose 模型和 Redis 的服务)编写一个单元测试。
courses.repository.ts:
import Injectable, HttpException, NotFoundException from "@nestjs/common";
import InjectModel from "@nestjs/mongoose"
import Course from "../../../../shared/course";
import Model from "mongoose";
import RedisService from 'nestjs-redis';
@Injectable()
export class CoursesRepository
private redisClient;
constructor(
@InjectModel('Course') private courseModel: Model<Course>,
private readonly redisService: RedisService,
)
this.redisClient = this.redisService.getClient();
async findAll(): Promise<Course[]>
const courses = await this.redisClient.get('allCourses');
if (!courses)
console.log('return from DB');
const mongoCourses = await this.courseModel.find();
await this.redisClient.set('allCourses', JSON.stringify(mongoCourses), 'EX', 20);
return mongoCourses;
console.log('return from cache');
return JSON.parse(courses);
测试是这样初始化的:
beforeEach(async () =>
const moduleRef = await Test.createTestingModule(
imports: [
MongooseModule.forRoot(MONGO_CONNECTION,
useNewUrlParser: true,
useUnifiedTopology: true
),
MongooseModule.forFeature([
name: "Course", schema: CoursesSchema ,
name: "Lesson", schema: LessonsSchema
]),
RedisModule.register()
],
controllers: [CoursesController, LessonsController],
providers: [
CoursesRepository,
LessonsRepository
],
).compile();
coursesRepository = moduleRef.get<CoursesRepository>(CoursesRepository);
redisClient = moduleRef.get<RedisModule>(RedisModule);
);
我的课程存储库服务有 2 个依赖项 - Redis 和 Mongoose 模型(课程)。 我想嘲笑他们两个。
如果我在模拟提供程序,我会使用该语法:
providers: [
provide: CoursesRepository, useFactory: mockCoursesRepository,
LessonsRepository
],
我可以创建一个模拟 Redis 服务,在测试期间代替实际的 Redis 服务使用吗?
如何?
谢谢, 亚龙
【问题讨论】:
你能显示你的CoursesRepository
的代码吗?
【参考方案1】:
您可以像其他任何依赖项一样模拟您的RedisService
。由于您真正对 Redis 客户端而不是服务感兴趣,因此您必须为服务创建一个 intermediate 模拟。对于猫鼬,您需要getModelToken
方法来获取正确的注入令牌,请参阅此answer:
const redisClientMockFactory = // ...
const redisServiceMock = getClient: () => redisClientMockFactory()
providers: [
provide: RedisService, useValue: redisServiceMock ,
provide: getModelToken('Course'), useFactory: courseModelMockFactory ,
CoursesRepository
],
还请注意,您可能不应该在单元测试中导入模块(除非它是一个测试模块)。请参阅此answer,了解单元测试和 e2e 测试之间的区别。
如何创建模拟?
看到这个answer。
【讨论】:
【参考方案2】:请注意,接受的答案模拟了一个提供者,如果它也由TestingModule
提供,它将满足您的测试类的依赖关系。但是,它并没有像要求的那样嘲笑import
s,这可能会有所不同。例如。如果您需要 import
一些也依赖于模拟值的模块,这将不起作用:
// DOES NOT WORK
const module = await Test.createTestingModule(
imports: [
ModuleThatNeedsConfig,
],
providers: [
provide: ConfigService, useValue: mockConfig ,
ExampleService, // relies on something from ModuleThatNeedsConfig
]
).compile();
要模拟import
,您可以使用导出模拟值的DynamicModule
:
const module = await Test.createTestingModule(
imports: [
module: class FakeModule ,
providers: [ provide: ConfigService, useValue: mockConfig ],
exports: [ConfigService],
,
ModuleThatNeedsConfig,
],
providers: [
ClassUnderTest,
]
).compile();
因为这有点冗长,所以像这样的实用函数可能会有所帮助:
export const createMockModule = (providers: Provider[]): DynamicModule =>
const exports = providers.map((provider) => (provider as any).provide || provider);
return
module: class MockModule ,
providers,
exports,
global: true,
;
;
然后可以像这样使用:
const module = await Test.createTestingModule(
imports: [
createMockModule([ provide: ConfigService, useValue: mockConfig ]),
ModuleThatNeedsConfig,
],
providers: [
ClassUnderTest,
]
).compile();
这通常有额外的好处,让您import
被测类的模块,而不是让测试绕过模块和 provide
直接值。
const module = await Test.createTestingModule(
imports: [
createMockModule([ provide: ConfigService, useValue: mockConfig ]),
ModuleThatNeedsConfig,
ModuleUnderTest,
],
).compile();
const service = module.get(ClassUnderTest);
【讨论】:
谢谢,在这里很好地使用了动态模块。我有一个非常复杂的模块,它对第 3 方动态模块 AgendaModule 具有前向引用依赖关系,该模块需要一个无法在测试用例中提供的值。您的解决方案帮助我解决了问题。我使用动态模块并从其导入中绕过议程模块,保持其他导入不变。并且,还为相关服务提供了模拟自定义提供程序。以上是关于如何开玩笑模拟nestjs导入?的主要内容,如果未能解决你的问题,请参考以下文章
NestJS:如何在 canActivate 中模拟 ExecutionContext