NestJS+TypeORM单元测试报save方法未定义错误排查
问题背景
我刚接触单元测试、TypeORM与NestJS,正在给用户注册功能的仓储层编写单元测试。注册业务本身运行正常,可以顺利完成用户注册,但执行仓储单元测试时抛出错误:
Cannot read properties of undefined (reading 'save')
相关代码
user.repository.ts
import { ConflictException, InternalServerErrorException, } from '@nestjs/common'; import { EntityRepository, Repository } from 'typeorm'; import { genSalt, hash } from 'bcryptjs'; import { omit } from 'lodash'; import { generate } from 'randomstring'; import { CreateUserDto } from 'src/dtos/user/create-user-dto'; import { User } from './user.entity'; @EntityRepository(User) export class UserRepository extends Repository<User> { async register(createUserDto: CreateUserDto): Promise<object> { const { firstName, ... //other destructured fields here } = createUserDto; const salt = await genSalt(); const user = this.create(); user.firstName = firstName; ... // created user here try { await this.save(user); } catch (error) { if (error.code === '23505') { //duplicate error console.log(error.message); // throw new ConflictException('Username or Password is Taken'); } else { throw new InternalServerErrorException('THIS IS A 500 ERROR'); } } let omitPasswordFromReturn = omit(user, ['password']); return omitPasswordFromReturn; } private async hashPassword(password: string, salt: string): Promise<string> { return hash(password, salt); } }
user.repository.spec.ts
import { ConflictException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { CreateUserDto } from 'src/dtos/user/create-user-dto'; import { UserRepository } from './user.repository'; const mockCreateUserDto: CreateUserDto = { firstName: 'Muhammad', ... }; describe('UserRepository', () => { let userRepository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UserRepository], }).compile(); userRepository = await module.get<UserRepository>(UserRepository); }); describe('user registration', () => { let save; beforeEach(() => { save = jest.fn(); userRepository.create = jest.fn().mockReturnValue({ save }); }); it('should be defined', () => { expect(userRepository.register).toBeDefined(); }) it('should sucessfully register user', async () => { save.mockResolvedValue(undefined); await expect( userRepository.register(mockCreateUserDto), ).resolves.not.toThrow(); }); }); });
我已经查阅TypeORM官方文档,也在StackOverflow检索了相关问题,但均未找到有效解决方案,目前已无排查思路。
错误原因
你mock错了save方法的挂载位置:
- 业务代码里调用的
await this.save(user),save是UserRepository继承自TypeORM Repository类的实例方法,挂载在仓储对象本身 - 测试代码里错误地把mock的
save挂在了this.create()返回的用户实体对象上,业务逻辑根本不会调用这个方法 - 测试环境没有启动真实数据库连接,仓储实例原生的
save方法缺少运行上下文,执行时就会抛出读取undefined属性的错误
修复方法
调整测试里的mock逻辑,直接mock仓储实例自身的方法即可,顺带把bcrypt的genSalt也mock掉,避免测试时真的执行密码哈希拖慢速度,修改后的测试核心代码如下:
// 顶部先mock bcrypt,避免真的执行加密逻辑 jest.mock('bcryptjs', () => ({ genSalt: jest.fn().mockResolvedValue('test-salt'), hash: jest.fn().mockResolvedValue('hashed-password') })) // 记得补充导入InternalServerErrorException用于异常场景测试 import { ConflictException, InternalServerErrorException } from '@nestjs/common'; describe('UserRepository', () => { let userRepository: UserRepository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UserRepository], }).compile(); // module.get是同步方法,不需要加await userRepository = module.get<UserRepository>(UserRepository); // 直接mock仓储实例的create和save方法,不要挂在create返回的对象上 jest.spyOn(userRepository, 'create').mockReturnValue({ firstName: 'Muhammad', // 补充其他mock的用户字段 password: 'hashed-password' } as User); jest.spyOn(userRepository, 'save').mockResolvedValue(undefined); }); describe('user registration', () => { it('should be defined', () => { expect(userRepository.register).toBeDefined(); }) it('should sucessfully register user', async () => { const result = await userRepository.register(mockCreateUserDto); // 校验save被正确调用 expect(userRepository.save).toHaveBeenCalled(); // 校验返回结果去掉了密码字段 expect(result).not.toHaveProperty('password'); }); it('should throw 500 error when save fails with non-duplicate error', async () => { (userRepository.save as jest.Mock).mockRejectedValue({ code: '12345' }); await expect(userRepository.register(mockCreateUserDto)).rejects.toThrow(InternalServerErrorException); }) }); });
调整后测试即可正常执行,不会再报找不到save的错误。
内容的提问来源于stack exchange,提问作者MSadiq
相关产品推荐
相关产品推荐

