Goutam B Seervi
Goutam B Seervi

Reputation: 1226

Unit Testing Nestjs Mongoose services

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.

Upvotes: 2

Views: 7213

Answers (1)

Pooya Haratian
Pooya Haratian

Reputation: 785

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();

   ...

Upvotes: 13

Related Questions