1

using nestjs framework and with a repository class that uses mongoose to do the CRUD operations we have a simple users.repository.ts file like this:

@Injectable()
export class UserRepository {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
  async create(createUserInput: CreateUserInput) {
      const createdUser = new this.userModel(createUserInput);
      return await createdUser.save();
    }
  }

  async findById(_id: MongooseSchema.Types.ObjectId) {
    return await this.userModel.findById(_id).exec();
  }

and it works normally when the server is up.
consider this users.repository.spec file :

import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { Model } from 'mongoose';

// User is my class and UserDocument is my typescript type
// ie. export type UserDocument = User & Document; <-- Mongoose Type
import { User, UserDocument } from '../domain/user.model';
import { UserRepository } from './users.repository';
//import graphqlScalars from 'graphql-scalar-types';

describe('UsersRepository', () => {
  let mockUserModel: Model<UserDocument>;
  let mockRepository: UserRepository;

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        {
          provide: getModelToken(User.name),
          useValue: Model, // <-- Use the Model Class from Mongoose
        },
        UserRepository,
        //graphqlScalars,
      ],
    }).compile();
    // Make sure to use the correct Document Type for the 'module.get' func
    mockUserModel = module.get<Model<UserDocument>>(getModelToken(User.name));
    mockRepository = module.get<UserRepository>(UserRepository);
  });

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

  it('should return a user doc', async () => {
    // arrange
    const user = new User();
    const userId = user._id;
    const spy = jest
      .spyOn(mockUserModel, 'findById') // <- spy 
      .mockResolvedValue(user as UserDocument); // <- set resolved value
    // act
    await mockRepository.findById(userId);
    // assert
    expect(spy).toBeCalled();
  });
});

so my question: for the should return a user doc test i get TypeError: metatype is not a constructor when and i guess

.mockResolvedValue(user as UserDocument); should be fixed. Note:graphql is used the query to the API and i have no idea that if the scalars should be provieded or not, if i uncomment the scalar, the expect(mockRepository).toBeDefined(); test would not pass any more

so any idea to fix the test would be apreciated.

Amir Meyari
  • 573
  • 4
  • 9
  • 30
  • 1
    [This example might be helpful](https://github.com/jmcdo29/testing-nestjs/blob/main/apps/mongo-sample/src/cat/cat.service.spec.ts#L96). It uses a packages called [@golevelup/ts-jest](https://github.com/golevelup/nestjs/tree/master/packages/testing) to auto-mock the model. – Jay McDoniel Apr 05 '22 at 22:57
  • @JayMcDoniel similar typeError when i adopted your repo, may you have a look ? https://stackoverflow.com/questions/72025468/nestjs-mongoose-unit-test-typeerror-functionname-is-not-a-function – Amir Meyari Apr 27 '22 at 08:39

2 Answers2

0

to handle a chained .exec we should define it via mockReturnThis():

static findById = jest.fn().mockReturnThis();

I needed the constructor to be called via new so i preferd to define a mock class in this way:

class UserModelMock {
  constructor(private data) {}
  new = jest.fn().mockResolvedValue(this.data);
  save = jest.fn().mockResolvedValue(this.data);
  static find = jest.fn().mockResolvedValue(mockUser());
  static create = jest.fn().mockResolvedValue(mockUser());
  static remove = jest.fn().mockResolvedValueOnce(true);
  static exists = jest.fn().mockResolvedValue(false);
  static findOne = jest.fn().mockResolvedValue(mockUser());
  static findByIdAndUpdate = jest.fn().mockResolvedValue(mockUser());
  static findByIdAndDelete = jest.fn().mockReturnThis();
  static exec = jest.fn();
  static deleteOne = jest.fn().mockResolvedValue(true);
  static findById = jest.fn().mockReturnThis();
}
Amir Meyari
  • 573
  • 4
  • 9
  • 30
0

I had a similar problem and using ideas Amir Meyari's anwser, I came up with a solution. Applied to this would be:

it('should return a user doc', async () => {
   // arrange
   const user = new User();
   const userId = user._id;
   const spy = jest
      .spyOn(mockUserModel, 'findById') // <- spy 
      .mockReturnThis()
      .mockReturnValue({
          exec: jest.fn().mockResolvedValue(user as UserDocument); // <- set resolved value
      } as unknown as Query<UserDocument, any>)  //you'll need to import Query from 'mongoose'
   // act
   await mockRepository.findById(userId);
   // assert
   expect(spy).toBeCalled();
});
Koji D'infinte
  • 1,309
  • 12
  • 20