I've been trying to figure out how to unit test services NestJS. So I wrote a spec file which tests these NestJS services which use mongoose using jest.
The spec file is as follows:
import { Test, TestingModule } from '@nestjs/testing';
import { EventsService } from './events.service';
import { getModelToken } from '@nestjs/mongoose';
import { CreateEventDto } from './dto/create-event.dto';
const event = {
_id: '53d53d2s',
name: 'Event Name',
description: 'Description of the event.',
min_team_size: 0,
max_team_size: 4,
event_price: 300,
};
describe('EventsService', () => {
let service: EventsService;
const eventModel = {
save: jest.fn().mockResolvedValue(event),
find: jest.fn().mockResolvedValue([event]),
findOne: jest.fn().mockResolvedValue(event),
findOneAndUpdate: jest.fn().mockResolvedValue(event),
deleteOne: jest.fn().mockResolvedValue(true),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
EventsService,
{
provide: getModelToken('Event'),
useValue: eventModel,
},
],
}).compile();
service = module.get<EventsService>(EventsService);
});
it('should return all the events', () => {
expect(service.findAll()).resolves.toEqual([event]).catch( err => {
console.log(err);
});
});
it('should create a new event and save it', () => {
expect(service.create({
name: event.name,
description: event.description,
event_price: event.event_price,
max_team_size: event.max_team_size,
min_team_size: event.min_team_size
} as CreateEventDto )).resolves.toEqual(event).catch(err => {
console.log(err);
});
});
it('should return one event', () => {
expect(service.findOne('53d53d2s')).resolves.toEqual(event).catch(err => {
console.log(err);
});
});
it('should find and update one model', () => {
expect(service.findOneAndUpdate('53d53d2s', {} as any)).resolves.toEqual(event).catch( err => {
console.log(err);
});
});
it('should delete one model', () => {
expect(service.deleteOne('53d53d2s')).resolves.toBeTruthy().catch( err => {
console.log(err);
});
});
});
This is the service file:
import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { CreateEventDto } from './dto/create-event.dto';
import { EventInterface } from './interfaces/event.interface';
import { InjectModel } from '@nestjs/mongoose';
@Injectable()
export class EventsService {
constructor(@InjectModel('Event') private readonly eventModel: Model<EventInterface>) {}
async create(createEventDto: CreateEventDto): Promise<EventInterface> {
const createdEvent = new this.eventModel(createEventDto);
return createdEvent.save();
}
async findAll(): Promise<EventInterface[]> {
return this.eventModel.find();
}
async findOne(id): Promise<EventInterface> {
return this.eventModel.findOne({ _id: id });
}
async findOneAndUpdate(id, createEventDTO: CreateEventDto): Promise<EventInterface> {
return this.eventModel.findOneAndUpdate({ _id: id }, createEventDTO);
}
async deleteOne(id): Promise<any> {
return this.eventModel.deleteOne({ _id: id});
}
}
Here all tests past successfully except the create(). It shows the following error:
{ Error: expect(received).resolves.toEqual()
Received promise rejected instead of resolved
Rejected to value: [TypeError: this.eventModel is not a constructor]
at expect (/home/rakesh/SJPUC/Equinox-2019/equinox-api/node_modules/expect/build/index.js:138:15)
at Object.it (/home/rakesh/SJPUC/Equinox-2019/equinox-api/src/events/events.service.spec.ts:44:5)
at Object.asyncJestTest (/home/rakesh/SJPUC/Equinox-2019/equinox-api/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
at resolve (/home/rakesh/SJPUC/Equinox-2019/equinox-api/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/home/rakesh/SJPUC/Equinox-2019/equinox-api/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/home/rakesh/SJPUC/Equinox-2019/equinox-api/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
matcherResult: undefined,
Also, I would like to know a better way to unit test these services.
The problem is, NesjJS will inject the eventModel object that you defined in the test file, into the EventService. Since it's an object, it doesn't have a constructor.
Instead of an object, you can pass a class like this:
class EventModel {
constructor(private data) {}
save = jest.fn().mockResolvedValue(this.data);
static find = jest.fn().mockResolvedValue([event]);
static findOne = jest.fn().mockResolvedValue(event);
static findOneAndUpdate = jest.fn().mockResolvedValue(event);
static deleteOne = jest.fn().mockResolvedValue(true);
}
describe('EventsService', () => {
let service: EventsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
EventsService,
{
provide: getModelToken('Event'),
useValue: EventModel,
},
],
}).compile();
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With