Jest测试registerUser用户注册方法报用户已存在错误排查
问题排查结论
报错核心原因是单元测试Mock逻辑配置错误,叠加测试用例本身逻辑冲突:
- 你在
beforeEach中给userModel.findOne写死了固定返回值userModelTest[0],无论传入什么用户名查询,都会返回一个已存在的用户对象,registerUser执行到用户存在判断分支时必然抛出异常,和你传入的testUser1用户名没有任何关系。 - 测试用例逻辑倒置:你在测试新建用户成功场景前,主动调用
findOne还期望返回true(即用户存在),和「新建不存在用户」的测试目标完全冲突。 - 存在缺失的Mock项:密码加密工具类
Criptography、WebSocket网关的emitnewUser方法没有做Mock,后续执行到对应逻辑会触发新的运行错误。 - 返回值断言错误:你期望返回结果和传入的明文
data完全相等,但业务逻辑会对密码做哈希加密,返回的用户对象密码是密文,不可能和明文入参一致;同时你Mock的create方法返回的role字段为operador,和入参的operar也不匹配。
修复方案
1. 修正基础Mock配置
首先调整仓库Mock的默认返回值,补全缺失的依赖Mock:
- 将
findOne的默认返回值改为null(模拟用户不存在的默认场景),需要测试用户已存在场景时再单独覆写返回值 - 新增密码加密工具类的Mock
- 新增WebSocket网关的Mock,不要直接引入真实的网关实例
修改后的beforeEach核心代码参考:
// 顶部先Mock加密工具,替换为你项目中Criptography的实际引入路径 jest.mock('../criptography/actual-path', () => ({ Criptography: { encodePwd: jest.fn().mockResolvedValue('$2b$10$mockhashedpassword') } })); // 测试文件顶部定义网关Mock方法 const mockEmitNewUser = jest.fn(); // beforeEach内的模块配置调整 beforeEach(async () => { const userMockRepository = { find: jest.fn().mockResolvedValue(userModelTest), findAll: jest.fn().mockResolvedValue(userModelTest), findById: jest.fn(), // 原来固定返回userModelTest[0]改为默认返回null findOne: jest.fn().mockResolvedValue(null), exec: jest.fn(), findByIdAndUpdate: jest.fn(), // 注意原代码此处拼写错误,少了末尾的e findOneAndDelete: jest.fn(), listUsers: jest.fn(), // create返回值和测试入参对齐,密码使用加密后的mock值 create: jest.fn().mockImplementation((user) => Promise.resolve({ _id: 'mock_id', ...user, password: '$2b$10$mockhashedpassword' })) }; const module: TestingModule = await Test.createTestingModule({ providers: [ Userservice, // 替换真实AppGateway为Mock实现 { provide: AppGateway, useValue: { emitnewUser: mockEmitNewUser } }, { provide: getModelToken('User'), useValue: userMockRepository, }, ], }).compile(); userService = module.get<Userservice>(Userservice); userRepository = module.get<Model<User>>(getModelToken('User')); // 每个用例执行前清空Mock调用记录,避免用例间互相影响 jest.clearAllMocks(); });
2. 修正测试用例逻辑
删除用例中冲突的断言,补充正确的逻辑校验,修改后的测试代码参考:
describe('Create', () => { it('should create new user successfully when username not exists', async () => { const data: createUser = { username: 'testUser1', password: '123456', name: 'teste1', role: 'operador', // 和Mock返回值对齐 } const result = await userService.registerUser(data); // 校验用户查重逻辑被正确调用 expect(userRepository.findOne).toHaveBeenCalledWith({ username: data.username }); // 校验密码加密逻辑被正确触发 expect(Criptography.encodePwd).toHaveBeenCalledWith('123456'); // 校验创建逻辑传入的是加密后的密码 expect(userRepository.create).toHaveBeenCalledWith({ ...data, password: '$2b$10$mockhashedpassword' }); // 校验WebSocket新用户推送被触发 expect(mockEmitNewUser).toHaveBeenCalledWith(result); // 校验返回值符合预期 expect(result).toEqual({ _id: 'mock_id', ...data, password: '$2b$10$mockhashedpassword' }); }); // 单独补充用户已存在的异常测试用例 it('should throw BadRequestException when username already exists', async () => { const data: createUser = { username: 'testUser50', // 和预置测试数据的用户名一致 password: '123456', name: 'teste1', role: 'operador', } // 临时覆写findOne返回值,模拟用户已存在场景 userRepository.findOne.mockResolvedValueOnce(userModelTest[0]); await expect(userService.registerUser(data)).rejects.toThrow(BadRequestException, 'Usuario ja existe.'); // 校验异常场景下后续逻辑不会执行 expect(userRepository.create).not.toHaveBeenCalled(); expect(mockEmitNewUser).not.toHaveBeenCalled(); }); });
额外注意点
- 单元测试不要引入真实的外部依赖(比如这里的真实WebSocket网关、真实加密方法),所有外部依赖都要做Mock,保证测试只聚焦当前服务层的逻辑。
- 同一个Mock方法的默认返回值要适配大多数测试场景,特殊场景可以用
mockResolvedValueOnce临时覆写,不要写死固定返回值导致所有用例都走同一分支。
内容的提问来源于stack exchange,提问作者Juliana Oliveira
相关产品推荐
相关产品推荐

