skunight / nestjs-redis

nestjs redis module
MIT License
322 stars 173 forks source link

In Jest UT, Nest can't resolve dependencies of the RedisService (?). Please make sure that the argument Symbol(REDIS_CLIENT) at index [0] is available in the RootTestModule context. #91

Open eharaj1 opened 3 years ago

eharaj1 commented 3 years ago

I have written the simple UT in Jest and also used nestjs-redis module for redis operation when i run the UT i am getting following error, i tried to mock as fn() but getting same issue.

Nest can't resolve dependencies of the RedisService (?). Please make sure that the argument Symbol(REDIS_CLIENT) at index [0] is available in the RootTestModule context.

    Potential solutions:
    - If Symbol(REDIS_CLIENT) is a provider, is it part of the current RootTestModule?
    - If Symbol(REDIS_CLIENT) is exported from a separate @Module, is that module imported within RootTestModule?
      @Module({
        imports: [ /* the Module containing Symbol(REDIS_CLIENT) */ ]
      })

Below is the code

import { Test, TestingModule } from '@nestjs/testing';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { getModelToken } from '@nestjs/mongoose';
import { RedisService } from '../../shared/lib/redis/redis.service';
describe('CatsController', () => {
    let controller: CatsController;
    let service: CatsService;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [CatsController],
      providers: [CatsService, RedisService, { provide: getModelToken('Cats'), useValue: { Symbol: jest.fn()} }],
    }).compile();

    controller = app.get<CatsController>(CatsController);
    service = app.get<CatsService>(CatsService);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
    expect(service).toBeDefined();
  });

  const catsData = [{
      cat_name: "cat",
      cat_type: "type",
      cat_color: "black"
  }]
  describe('Cats List', () => {

    it('should return all cats', async() => {
        jest.spyOn(service, 'getAll').mockResolvedValue({data: catsData, success: true})
        const catsList = await controller.findAll()
      expect(catsList).toBe({data: catsData, success: true});
    });

    it('should throw error record not found', async() => {
        jest.spyOn(service, 'getAll').mockRejectedValue({message: 'Records not found'})
        try{
            await controller.findAll();
          }catch(e){
            expect(e.message).toBe('Records not found');
          }
      });

  });
});
eharaj1 commented 3 years ago

Hello,

Please help me on this.

rjpkuyper commented 3 years ago

As far as I see you are not importing the RedisModule in your test.

eharaj1 commented 3 years ago

Hi @rjpkuyper I imported the RedisModule also but getting same error.

rjpkuyper commented 3 years ago

Can you share a minimal repository to replicate?

eharaj1 commented 3 years ago

Hi @rjpkuyper I have shared the repo with you. Please check it out.

rjpkuyper commented 3 years ago

See your repo branch fix/tests for updatet repo. For the commit see: https://github.com/eharaj1/nestjs-redis-mongo-opentelemetry-jaeger/pull/1/commits/15b7b9ccc23df761644442d2ebfc92dda14b7c90 .

  1. You are not actively overriding the RedisService and thus it cannot resolve. You have to explicitly override the RedisService to prevent connecting with a real Redis instance.
  2. On some places you did a toBe or toEqual check, but you want to match an object instead. Thus use matchObject instead.
  3. You don't need to do a beforeEach, use beforeAll instead.

So what I did, is to override the providers with a mock instead.

// cats.controller.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { getModelToken } from '@nestjs/mongoose';

import { RedisService } from '../../shared/lib/redis/redis.service';
import {REDIS_CLIENT} from "../../shared/lib/redis/redis.constants";

describe('CatsController', () => {
    let controller: CatsController;
    let service: CatsService;

  beforeAll(async () => {
    const app: TestingModule = await Test.createTestingModule({
      imports: [],
      controllers: [CatsController],
      providers: [
        CatsService, 
        { 
          provide: getModelToken('Cats'), 
          useValue: { Symbol: jest.fn()} 
        },
        {
          provide: RedisService,
          useValue: {
            get: jest.fn(),
            getClient: jest.fn().mockReturnValue(REDIS_CLIENT),
          }
        },
        {
          provide: REDIS_CLIENT,
          useValue: {
            get: jest.fn().mockReturnValue('foo'),
            keys: jest.fn().mockReturnValue(['foo', 'bar']),
          }
        }
      ],
    }).compile();

    controller = app.get<CatsController>(CatsController);
    service = app.get<CatsService>(CatsService);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
    expect(service).toBeDefined();
  });

  const catsData = [{
      cat_name: "cat",
      cat_type: "type",
      cat_color: "black"
  }]
  describe('Cats List', () => {

    it('should return all cats', async() => {
      jest.spyOn(service, 'getAll').mockResolvedValue({data: catsData, success: true})
      const catsList = await controller.findAll();

      expect(catsList).toMatchObject({data: catsData, success: true});
    });

    it('should throw error record not found', async() => {
        jest.spyOn(service, 'getAll').mockRejectedValue({message: 'Records not found'})
        try{
            await controller.findAll();
          }catch(e){
            expect(e.message).toBe('Records not found');
          }
      });

  });
});
// cats.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
import { getModelToken } from '@nestjs/mongoose';

const mappingModel = {
    findOne: jest.fn(),
    find: jest.fn(),
    create: jest.fn(),
    };
describe('CatsService', () => {
    let service: CatsService;
    let model: typeof mappingModel;

  beforeAll(async () => {
    const app: TestingModule = await Test.createTestingModule({
        providers: [CatsService, {provide: getModelToken('Cats'), useValue: mappingModel}],
    }).compile();

    service = app.get<CatsService>(CatsService);
    model = app.get(getModelToken('Cats'));
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
    expect(model).toBeDefined();
  });

  const catsData = {
      data: [{
        cat_name: "cat",
        cat_type: "type",
        cat_color: "black"
        }]  
    }

  describe('Cats List', () => {

    it('should return all cats', async() => {
        model.find.mockResolvedValue(catsData);
        const res = await service.getAll();

        expect(res).toMatchObject({ data: catsData })
        expect(model.find).toHaveBeenCalledTimes(1);
    });

    it('should through in cats list', async() => {
        model.find.mockRejectedValue({message:'hub not found!!'});
        try{
            await service.getAll();
          }catch(e){
            expect(e.message).toBe('Record not found.');
            expect(model.find).toHaveBeenCalledTimes(2);
          }
      });

  });
});

Hope this helps.

eharaj1 commented 3 years ago

Hey @rjpkuyper ,

Many thanks, you saved my days.

rjpkuyper commented 3 years ago

Your welcome!

Could you please close the issue.