如何开玩笑模拟 Nestjs 导入?

Yar*_*ron 8 unit-testing node.js typescript jestjs nestjs

我想为我的 Nestjs“课程”存储库服务(依赖于 Mongoose 模型和 Redis 的服务)编写一个单元测试。

课程.存储库.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);
      }
}
Run Code Online (Sandbox Code Playgroud)

测试是这样初始化的:

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);

});
Run Code Online (Sandbox Code Playgroud)

我的课程存储库服务有 2 个依赖项 - Redis 和 Mongoose 模型(课程)。我想嘲笑他们两个。

如果我正在嘲笑一个提供者,我会使用该语法:

providers: [
    {provide: CoursesRepository, useFactory: mockCoursesRepository},
     LessonsRepository
    ],
Run Code Online (Sandbox Code Playgroud)

我可以创建一个模拟 Redis 服务来代替测试期间实际的 Redis 服务吗?

如何 ?

谢谢,亚龙

eri*_*2k8 11

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();
Run Code Online (Sandbox Code Playgroud)

要模拟import,您可以使用DynamicModule导出模拟值的 a :

const module = await Test.createTestingModule({
  imports: [
    {
      module: class FakeModule {},
      providers: [{ provide: ConfigService, useValue: mockConfig }],
      exports: [ConfigService],
    },
    ModuleThatNeedsConfig,
  ],
  providers: [
    ClassUnderTest,
  ]
}).compile();
Run Code Online (Sandbox Code Playgroud)

由于这有点冗长,像这样的实用函数可能会有所帮助:

export const createMockModule = (providers: Provider[]): DynamicModule => {
  const exports = providers.map((provider) => (provider as any).provide || provider);
  return {
    module: class MockModule {},
    providers,
    exports,
    global: true,
  };
};
Run Code Online (Sandbox Code Playgroud)

然后可以像这样使用:

const module = await Test.createTestingModule({
  imports: [
    createMockModule([{ provide: ConfigService, useValue: mockConfig }]),
    ModuleThatNeedsConfig,
  ],
  providers: [
    ClassUnderTest,
  ]
}).compile();
Run Code Online (Sandbox Code Playgroud)

这通常具有额外的好处,可以让您使用import被测类的模块,而不是让测试provide直接绕过模块和值。

const module = await Test.createTestingModule({
  imports: [
    createMockModule([{ provide: ConfigService, useValue: mockConfig }]),
    ModuleThatNeedsConfig,
    ModuleUnderTest,
  ],
}).compile();

const service = module.get(ClassUnderTest);
Run Code Online (Sandbox Code Playgroud)


Kim*_*ern 7

RedisService您可以像任何其他依赖项一样嘲笑您的依赖项。由于您真正感兴趣的是 Redis 客户端而不是服务,因此您必须为服务创建一个中间模拟。对于猫鼬,您需要getModelToken获取正确注入令牌的方法,请参阅此答案

const redisClientMockFactory = // ...
const redisServiceMock = {getClient: () => redisClientMockFactory()}

providers: [
  { provide: RedisService, useValue: redisServiceMock },
  { provide: getModelToken('Course'), useFactory: courseModelMockFactory },
  CoursesRepository
],
Run Code Online (Sandbox Code Playgroud)

另请注意,您可能不应该在单元测试中导入模块(除非它是测试模块)。请参阅有关单元测试和 e2e 测试之间区别的答案。

如何创建模拟?

看到这个答案