【问题标题】:How to jest mock nestjs imports?如何开玩笑模拟nestjs导入?
【发布时间】: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的代码吗?

标签: node.js typescript unit-testing jestjs nestjs


【解决方案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 具有前向引用依赖关系,该模块需要一个无法在测试用例中提供的值。您的解决方案帮助我解决了问题。我使用动态模块并从其导入中绕过议程模块,保持其他导入不变。并且,还为相关服务提供了模拟自定义提供程序。
    猜你喜欢
    • 2020-12-17
    • 2020-10-20
    • 2018-09-14
    • 2018-04-13
    • 2020-11-18
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    • 2018-07-28
    相关资源
    最近更新 更多