Nestjs jest unit test returns this.userModel is not a constructor

  Kiến thức lập trình

I am down to one failed unit test and for the past 3 days I have not been able to pass it. I am getting the followning error in the console related to the jest test:

FAIL  src/user/user.service.spec.ts (10.795 s)
  ● UserService › create › should create a new user

    InternalServerErrorException: this.userModel is not a constructor

      23 |         throw error;
      24 |       }
    > 25 |       throw new InternalServerErrorException(error.message);
         |             ^
      26 |     }
      27 |   }
      28 |

      at UserService.create (user/user.service.ts:25:13)
      at Object.<anonymous> (user/user.service.spec.ts:76:22)

Here is what I have for my user.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { UserService } from './user.service';
import { User, UserDocument } from '../schemas/user.schema';
import { ConflictException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { Model } from 'mongoose';

describe('UserService', () => {
  let service: UserService;
  let userModel: Model<UserDocument>;

  const mockUser = {
    _id: 'someId',
    username: 'testuser',
    email: '[email protected]',
    password: 'hashedpassword',
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getModelToken(User.name),
          useValue: {
            new: jest.fn().mockResolvedValue(mockUser),
            constructor: jest.fn().mockResolvedValue(mockUser),
            findOne: jest.fn(),
            findById: jest.fn(),
            find: jest.fn(),
            create: jest.fn(),
            deleteOne: jest.fn(),
            deleteMany: jest.fn(),
            exec: jest.fn(),
          },
        },
      ],
    }).compile();
  
    service = module.get<UserService>(UserService);
    userModel = module.get<Model<UserDocument>>(getModelToken(User.name));
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('create', () => {
    it('should create a new user', async () => {
      const createUserDto: CreateUserDto = {
        username: 'newuser',
        email: '[email protected]',
        password: 'password123',
      };
    
      // Mock findOne to return null (user doesn't exist)
      jest.spyOn(userModel, 'findOne').mockReturnValue({
        exec: jest.fn().mockResolvedValue(null),
      } as any);
    
      // Mock the 'create' operation on the model
      const mockCreatedUser = {
        _id: 'someNewId',
        ...createUserDto,
        save: jest.fn().mockResolvedValue({
          _id: 'someNewId',
          ...createUserDto,
        }),
      };
      jest.spyOn(userModel, 'create').mockResolvedValue(mockCreatedUser as any);
    
      const result = await service.create(createUserDto);
      expect(result).toEqual(expect.objectContaining({
        username: createUserDto.username,
        email: createUserDto.email,
      }));
    
      // Verify that findOne was called with the correct parameters
      expect(userModel.findOne).toHaveBeenCalledWith({
        $or: [{ username: createUserDto.username }, { email: createUserDto.email }]
      });
    
      // Verify that 'create' was called with the correct parameters
      expect(userModel.create).toHaveBeenCalledWith(createUserDto);
    });

    it('should throw ConflictException if username or email already exists', async () => {
      const createUserDto: CreateUserDto = {
        username: 'existinguser',
        email: '[email protected]',
        password: 'password123',
      };

      // Mock findOne to return an existing user
      jest.spyOn(userModel, 'findOne').mockReturnValue({
        exec: jest.fn().mockResolvedValue(mockUser),
      } as any);

      await expect(service.create(createUserDto)).rejects.toThrow(ConflictException);

      // Verify that findOne was called with the correct parameters
      expect(userModel.findOne).toHaveBeenCalledWith({
        $or: [{ username: createUserDto.username }, { email: createUserDto.email }]
      });
    });
  });

  describe('findOne', () => {
    it('should find a user by username', async () => {
      jest.spyOn(userModel, 'findOne').mockReturnValue({
        exec: jest.fn().mockResolvedValue(mockUser),
      } as any);

      const result = await service.findOne('testuser');
      expect(result).toEqual(mockUser);
    });

    it('should throw NotFoundException if user not found', async () => {
      jest.spyOn(userModel, 'findOne').mockReturnValue({
        exec: jest.fn().mockResolvedValue(null),
      } as any);

      await expect(service.findOne('nonexistent')).rejects.toThrow(NotFoundException);
    });
  });

  describe('findById', () => {
    it('should find a user by id', async () => {
      jest.spyOn(userModel, 'findById').mockReturnValue({
        exec: jest.fn().mockResolvedValue(mockUser),
      } as any);

      const result = await service.findById('someId');
      expect(result).toEqual(mockUser);
    });

    it('should throw NotFoundException if user not found', async () => {
      jest.spyOn(userModel, 'findById').mockReturnValue({
        exec: jest.fn().mockResolvedValue(null),
      } as any);

      await expect(service.findById('nonexistentId')).rejects.toThrow(NotFoundException);
    });
  });

  describe('findAll', () => {
    it('should return an array of users', async () => {
      jest.spyOn(userModel, 'find').mockReturnValue({
        exec: jest.fn().mockResolvedValue([mockUser]),
      } as any);

      const result = await service.findAll();
      expect(result).toEqual([mockUser]);
    });

    it('should throw InternalServerErrorException on database error', async () => {
      jest.spyOn(userModel, 'find').mockReturnValue({
        exec: jest.fn().mockRejectedValue(new Error('Database error')),
      } as any);

      await expect(service.findAll()).rejects.toThrow(InternalServerErrorException);
    });
  });

  describe('deleteById', () => {
    it('should delete a user by id', async () => {
      jest.spyOn(userModel, 'deleteOne').mockReturnValue({
        exec: jest.fn().mockResolvedValue({ deletedCount: 1 }),
      } as any);

      const result = await service.deleteById('someId');
      expect(result).toBe(true);
    });

    it('should throw NotFoundException if user not found', async () => {
      jest.spyOn(userModel, 'deleteOne').mockReturnValue({
        exec: jest.fn().mockResolvedValue({ deletedCount: 0 }),
      } as any);

      await expect(service.deleteById('nonexistentId')).rejects.toThrow(NotFoundException);
    });
  });

  describe('deleteAll', () => {
    it('should delete all users', async () => {
      jest.spyOn(userModel, 'deleteMany').mockReturnValue({
        exec: jest.fn().mockResolvedValue({}),
      } as any);

      await expect(service.deleteAll()).resolves.not.toThrow();
    });

    it('should throw InternalServerErrorException on database error', async () => {
      jest.spyOn(userModel, 'deleteMany').mockReturnValue({
        exec: jest.fn().mockRejectedValue(new Error('Database error')),
      } as any);

      await expect(service.deleteAll()).rejects.toThrow(InternalServerErrorException);
    });
  });
});

and finally, here is what I have for the user.service.ts

import { Injectable, ConflictException, NotFoundException, InternalServerErrorException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from '../schemas/user.schema';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    try {
      const existingUser = await this.userModel.findOne({
        $or: [{ username: createUserDto.username }, { email: createUserDto.email }]
      }).exec();
      if (existingUser) {
        throw new ConflictException('Username or email already exists');
      }
      const user = new this.userModel(createUserDto);
      return await user.save();
    } catch (error) {
      if (error instanceof ConflictException) {
        throw error;
      }
      throw new InternalServerErrorException(error.message);
    }
  }

  async findOne(username: string): Promise<User> {
    const user = await this.userModel.findOne({ username }).exec();
    if (!user) {
      throw new NotFoundException(`User with username ${username} not found`);
    }
    return user;
  }

  async findById(id: string): Promise<User> {
    const user = await this.userModel.findById(id).exec();
    if (!user) {
      throw new NotFoundException(`User with id ${id} not found`);
    }
    return user;
  }

  async findAll(): Promise<User[]> {
    try {
      return await this.userModel.find().exec();
    } catch (error) {
      throw new InternalServerErrorException(error.message);
    }
  }

  async deleteById(id: string): Promise<boolean> {
    const result = await this.userModel.deleteOne({ _id: id }).exec();
    if (result.deletedCount === 0) {
      throw new NotFoundException(`User with id ${id} not found`);
    }
    return true;
  }

  async deleteAll(): Promise<void> {
    try {
      await this.userModel.deleteMany().exec();
    } catch (error) {
      throw new InternalServerErrorException(error.message);
    }
  }
}

Test Suites: 1 failed, 18 passed, 19 total
Tests: 1 failed, 94 passed, 95 total

If anyone has dealt with this before and can share with me what worked for you, I would sure appreciate it. If I am missing any code that you would like to see, please tell me and I will be happy to edit my question. Thank you in advance.

Theme wordpress giá rẻ Theme wordpress giá rẻ Thiết kế website

LEAVE A COMMENT