I am getting issues while unit testing my controller. For maximum coverage I wanted to unit test controller and respective services and would like to mock external dependencies like mongoose connection etc.
I am getting error "Nest can't resolve dependencies of my service". I already tried suggestions mentioned by @adrien2p below, but didn't get any luck:
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);
}
}
}
md5-f525588203ac7468793fc7bd42faae8b
@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);
}
}
}
md5-f525588203ac7468793fc7bd42faae8b
@Module({
imports: [MongooseModule.forFeature([{ name: 'devices', schema: DeviceSchema }])],
controllers: [DeviceController],
components: [DeviceService, ...deviceProviders],
})
export class DeviceModule { }
md5-f525588203ac7468793fc7bd42faae8b
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 code 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
The simplest way is to mock DeviceService when you want to test DeviceController.
Try replace real DeviceService with your mock implementation when you compile testing module.
Something like this:
// create mock class or you can use some package like sinon for this
class DeviceServiceMock extends BaseService {
async getDevices(group): Promise<any> {
return [];
}
}
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: [{
provide: DeviceService,
useValue: new DeviceServiceMock() // or alternatively useClass: DeviceServiceMock
}],
}).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);
});
});
});
Small notes
async getDevices(group): Promise<any> {
try {
return await this._deviceModel.find({ Group: group }).exec();
} catch (error) {
return Promise.reject(error);
}
}
you shouldn't use return await ...
in async
functions, because async function will return Promise in any case, you can just return already existing one instead of value. The result will be same in both cases:
return await this._deviceModel.find({ Group: group }).exec();
```
return this._deviceModel.find({ Group: group }).exec();
As for the your question, it's very hard to load all dependencies tree in the tests almost in all cases. Also when you load all dependencies, you need to be sure that all of them works without any errors. Because when your test fails, you need to know where something went wrong - in the tested class or in hes dependency.
That's why it is better to replace all dependencies for tested class class with mock implementations.
In some cases you might need to keep the real dependencies for tested class, but I don't know why may be required to keep dependencies of dependencies :)
E.g. in your case, you can keep `DeviceService` if it's required, but you should mock 'devices' anyway:
```typescript
// there must be DeviceModelMock implementation
const deviceProvidersMock = [
{
provide: 'devices',
useValue: DeviceModelMock,
},
];
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [DeviceController],
components: [DeviceService, ...deviceProvidersMock],
}).compile()
});
Thanks @andrew-yustyk for a huge explanation.
@mukeshrawat02, additionally, I would not recommend using @Res()
directly, it makes testing part tougher. Also, please use StackOverflow for such questions next time
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
Small notes
you shouldn't use
return await ...
inasync
functions, because async function will return Promise in any case, you can just return already existing one instead of value. The result will be same in both cases:```
return this._deviceModel.find({ Group: group }).exec();