skip to Main Content

I want to write a unit test for my nestjs ‘Course’ repository service (a service that has dependencies on Mongoose Model and 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);
      }
}

The test is initialized this way:

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

});

My Course repository service has 2 dependencies – Redis and Mongoose Model (Course).
I would like to mock both of them.

If I was mocking a provider I would use that syntax:

providers: [
    {provide: CoursesRepository, useFactory: mockCoursesRepository},
     LessonsRepository
    ],

Can I create a mock Redis service which will be used instead of the an actual Redis service during a test?

How ?

Thanks,
Yaron

2

Answers


  1. You can mock your RedisService just as any other dependency. Since you are really interested in the Redis client and not the service, you have to create an intermediate mock for the service. For mongoose, you need the getModelToken method for getting the correct injection token, see this answer:

    const redisClientMockFactory = // ...
    const redisServiceMock = {getClient: () => redisClientMockFactory()}
    
    providers: [
      { provide: RedisService, useValue: redisServiceMock },
      { provide: getModelToken('Course'), useFactory: courseModelMockFactory },
      CoursesRepository
    ],
    

    Please also note, that you probably should not import modules in unit tests (unless it is a testing module). See this answer on a distinction between unit and e2e tests.

    How to create mocks?

    See this answer.

    Login or Signup to reply.
  2. Note that the accepted answer mocks a provider, which will satisfy the dependencies of your class under test IF it is also supplied from the TestingModule with a provider. However, it’s not mocking imports as asked, which can be different. E.g. if you need to import some module which also depends on the mocked value, this will NOT work:

    // DOES NOT WORK
    const module = await Test.createTestingModule({
      imports: [
        ModuleThatNeedsConfig,
      ],
      providers: [
        { provide: ConfigService, useValue: mockConfig },
        ExampleService, // relies on something from ModuleThatNeedsConfig
      ]
    }).compile();
    

    To mock an import, you can use a DynamicModule that exports the mocked values:

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

    As that’s a bit verbose, a utility function such as this can be helpful:

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

    Which can then be used like:

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

    This often has the added benefit of letting you import the module for the class under test, rather than having the test bypass the module and provide the value directly.

    const module = await Test.createTestingModule({
      imports: [
        createMockModule([{ provide: ConfigService, useValue: mockConfig }]),
        ModuleThatNeedsConfig,
        ModuleUnderTest,
      ],
    }).compile();
    
    const service = module.get(ClassUnderTest);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search