如何开玩笑模拟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 提供,它将满足您的测试类的依赖关系。但是,它并没有像要求的那样嘲笑imports,这可能会有所不同。例如。如果您需要 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导入?的主要内容,如果未能解决你的问题,请参考以下文章

如何开玩笑地模拟 i18next 模块

Jest - 模拟函数,从另一个文件导入

NestJS:如何在 canActivate 中模拟 ExecutionContext

如何在服务nestjs中模拟getMongoRepository

开玩笑如何模拟 api 调用

如何开玩笑地模拟 VueAxios