How to mock Jest Redis in Nestjs
Asked Answered
P

3

6

I'm working on NestJs application and wrote unit test for my authenticateUser function in user.service.ts.It's has pass in my local machine.but when I deployed it in to server and run unit test, i got an error Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED.Seems like redis mock is not working.How should I mock redis and resolve this issue for working?

user.service.ts

async authenticateUser(authDto: AuthDTO): Promise<AuthResponse> {
    try {
     const userData = await this.userRepository.findOne({msisdn});
     if(userData){
         await this.redisCacheService.setCache(msisdn, userData);
     }
    } catch (error) {
        console.log(error)
    }
}

redisCache.service.ts

export class RedisCacheService {
  constructor(
    @Inject(CACHE_MANAGER) private readonly cache: Cache,
  ) {}

  async setCache(key, value) {
    await this.cache.set(key, value);
  }
}

user.service.spec.ts

describe('Test User Service', () => {
  let userRepository: Repository<UserEntity>;
  let userService: UserService;
  let redisCacheService: RedisCacheService;
  let cacheManager: any;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        UserEntity,
        RedisCacheService,
        {
          provide: getRepositoryToken(UserEntity),
          useClass: registeredApplicationRepositoryMockFactory,
        },
      ],
      imports: [CacheModule.register({})],
    }).compile();

    userService = module.get<UserService>(UserService);
    userRepository = module.get<Repository<UserEntity>>(
      getRepositoryToken(UserEntity),
    );
    redisCacheService = module.get<RedisCacheService>(RedisCacheService);
    cacheManager = module.get<any>(CACHE_MANAGER);
  });

  it('authenticateUser should return success response', async () => {
    const userEntity = { id: 1, name: 'abc', age: 25 };
    const mockSuccessResponse = new AuthResponse(
      HttpStatus.OK,
      STRING.SUCCESS,
      `${STRING.USER} ${STRING.AUTHENTICATE} ${STRING.SUCCESS}`,
      {},
    );

    jest.spyOn(userRepository, 'findOne').mockResolvedValueOnce(userEntity);
    jest.spyOn(redisCacheService, 'setCache').mockResolvedValueOnce(null);

    expect(await userService.authenticateUser(mockAuthBody)).toEqual(mockSuccessResponse);
  });
});

enter image description here

Planography answered 8/2, 2022 at 7:53 Comment(3)
Looks like your RedisCacheService is loading the @Inject(CACHE_MANAGER) which subsequently tries to connect to Redis. You can fully mock RedisCacheService see jestjs.io/docs/es6-class-mocks or jestjs.io/docs/manual-mocksManyplies
is it because of CACHE_MANAGER? can i mock entire redis here?Planography
You can use a npm redis-server and create a Redis instance before creating the TestingModule (in BeforEach). this way a temporary Redis-Server will be created for each test (just add AfterEach to remove it). But... if you are looking to write Unit-Test you should mock the RedisCacheService instead of adding a network component to the mix.Manyplies
U
13

You can mock CACHE_MANAGER using a custom provider:

import { CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';

describe('AppService', () => {
  let service: AppService;
  let cache: Cache;

  beforeEach(async () => {
    const app = await Test.createTestingModule({
      providers: [
        AppService,
        {
          provide: CACHE_MANAGER,
          useValue: {
            get: () => 'any value',
            set: () => jest.fn(),
          },
        },
      ],
    })
    .compile();

    service = app.get<AppService>(AppService);
    cache = app.get(CACHE_MANAGER);
  });

  // Then you can use jest.spyOn() to spy and mock

  it(`should cache the value`, async () => {
    const spy = jest.spyOn(cache, 'set');

    await service.cacheSomething();

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy.mock.calls[0][0]).toEqual('key');
    expect(spy.mock.calls[0][1]).toEqual('value');
  });

  it(`should get the value from cache`, async () => {
    const spy = jest.spyOn(cache, 'get');

    await service.getSomething();

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it(`should return the value from the cache`, async () => {
    jest.spyOn(cache, 'get').mockResolvedValueOnce('value');
    
    const res = await service.getSomething();

    expect(res).toEqual('value');
  }),
});

More details on Custom Providers: https://docs.nestjs.com/fundamentals/custom-providers

Two more things, for unit testing you shouldn't import modules but mock the dependencies instead. And as Daniel said, UserService is not using CACHE_MANAGER but RedisCacheService, so you should mock RedisCacheService.

Usually the best thing to do is to only provide the service you're testing and mock the dependencies.

Upbow answered 15/2, 2022 at 16:29 Comment(1)
Yes, that is very good way of mocking almost anything for testingAesculapian
C
1

To solve this problem I overrode the CACHE_MANAGER

beforeEach(async () => {
        const app = await Test.createTestingModule({
          providers: [
            AppService,
            {
              provide: CACHE_MANAGER,
              useValue: {
                get: () => 'any value',
                set: () => jest.fn(),
              },
            },
          ],
        }).overrideProvider(CACHE_MANAGER)
          .useValue({
              get: jest.fn(),
              set: jest:fn()
         }).compile();
    
        service = app.get<AppService>(AppService);
        cache = app.get(CACHE_MANAGER);
      });
Creuse answered 22/3, 2023 at 7:16 Comment(0)
A
0

in order to use the jest spy functions you need to return the jest function directly.

    providers: [
        AppService,
        {
          provide: CACHE_MANAGER,
          useValue: {
            get: () => 'any value',
            set: jest.fn(),
          },
        },
      ],
Apprehensible answered 18/11, 2022 at 10:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.