You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 17:09:19