Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test Controller and mock @InjectModel in the Service constructor

I am getting issues while unit testing my controller and getting an error "Nest can't resolve dependencies of my service".

For maximum coverage I wanted to unit test controller and respective services and would like to mock external dependencies like mongoose connection. For the same I already tried suggestions mentioned in the below link but didn't find any luck with that:

https://github.com/nestjs/nest/issues/194#issuecomment-342219043

Please find my code below:

export const deviceProviders = [
    {
        provide: 'devices',
        useFactory: (connection: Connection) => connection.model('devices', DeviceSchema),
        inject: ['DbConnectionToken'],
    },
];


export class DeviceService extends BaseService {
    constructor(@InjectModel('devices') private readonly _deviceModel: Model<Device>) {
        super();
    }

    async getDevices(group): Promise<any> {
        try {
            return await this._deviceModel.find({ Group: group }).exec();
        } catch (error) {
            return Promise.reject(error);
        }
    }
}


@Controller()
export class DeviceController {
    constructor(private readonly deviceService: DeviceService) {
    }

   @Get(':group')
   async getDevices(@Res() response, @Param('group') group): Promise<any> {
        try {
            const result = await this.deviceService.getDevices(group);
            return response.send(result);
        }
        catch (err) {
            return response.status(422).send(err);
        }
    }
}


@Module({
    imports: [MongooseModule.forFeature([{ name: 'devices', schema: DeviceSchema }])],
    controllers: [DeviceController],
    components: [DeviceService, ...deviceProviders],
})
export class DeviceModule { }

Unit test:

describe('DeviceController', () => {
    let deviceController: DeviceController;
    let deviceService: DeviceService;

    const response = {
        send: (body?: any) => { },
        status: (code: number) => response,
    };

    beforeEach(async () => {
        const module = await Test.createTestingModule({
            controllers: [DeviceController],
            components: [DeviceService, ...deviceProviders],
        }).compile();

        deviceService = module.get<DeviceService>(DeviceService);
        deviceController = module.get<DeviceController>(DeviceController);
    });

    describe('getDevices()', () => {
        it('should return an array of devices', async () => {
            const result = [{
                Group: 'group_abc',
                DeviceId: 'device_abc',
            },
            {
                Group: 'group_xyz',
                DeviceId: 'device_xyz',
            }];
            jest.spyOn(deviceService, 'getDevices').mockImplementation(() => result);

            expect(await deviceController.getDevices(response, null)).toBe(result);
        });
    });
});

When I am running my test case above, I am getting two errors:

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

Cannot spyOn on a primitive value; undefined given

like image 441
Mukesh Rawat Avatar asked Oct 18 '18 15:10

Mukesh Rawat


People also ask

What is unit testing and mock testing?

Mocking is a process used in unit testing when the unit being tested has external dependencies. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies.

What is the purpose of unit testing?

The main objective of unit testing is to isolate written code to test and determine if it works as intended. Unit testing is an important step in the development process, because if done correctly, it can help detect early flaws in code which may be more difficult to find in later testing stages.

Should unit test mock dependencies?

Correct. You should mock things that depend on anything persistent or external in order to prevent the test from depending on anything persistent or external.


2 Answers

Example code:

import { Test } from '@nestjs/testing';

import { getModelToken } from '@nestjs/mongoose';


describe('auth', () => {
  let deviceController: DeviceController;
  let deviceService: DeviceService;

  const mockRepository = {
    find() {
      return {};
    }
  };

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      imports: [DeviceModule]
    })
      .overrideProvider(getModelToken('Auth'))
      .useValue(mockRepository)
      .compile();

    deviceService = module.get<DeviceService>(DeviceService);
  });

  // ...


});
like image 118
jkchao Avatar answered Nov 16 '22 04:11

jkchao


You are not injecting the correct token here. Instead of a plain string you have to use the function getModelToken.

import { getModelToken } from '@nestjs/mongoose';

// ...

{ provide: getModelToken('devices'), useFactory: myFactory },
like image 32
Kim Kern Avatar answered Nov 16 '22 04:11

Kim Kern