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

Jest测试Mongoose异步函数时无法捕获重复键MongoServerError

问题根因

Jest不是捕获不到MongoServerError,是你的测试代码漏了异步等待,导致出现了未处理的Promise rejection:

  • 你第三组用例里第一次插入合法用户的断言写法完全不对:expect(async () => { await userRepo.save(userComplete); }).not.toThrow() 这行代码,toThrow()默认只能捕获函数同步执行时抛出的错误,async函数本身只会返回Promise,不会同步抛错,而且你没有await这个断言结果,等于第一次save操作成了没人管的悬空Promise——Jest执行完这行根本没等数据写入MongoDB,就直接往下跑第二次插入了。
  • 这时候第一次插入操作后续如果触发任何reject(包括你场景里的重复键,或者其他写入错误),都没有对应的catch逻辑处理,会被Node.js当成未捕获的Promise rejection直接抛出,Jest碰到这种未挂任何处理逻辑的异步错误就会直接报未捕获,和你后面写的断言没关系。
  • 额外提一句:Mongoose Schema里的unique: true不是模型级别的校验规则,只是用来给MongoDB创建唯一索引的标记,重复键错误是MongoDB服务端返回的MongoServerError,不是Mongoose校验阶段抛的ValidationError,但只要异步逻辑写对,这个错误完全可以被Jest正常捕获。你之前把断言改成错误文本的时候能看到实际收到的错误就是E11000,就说明第二次插入的错误其实已经被rejects捕获到了,只是第一次悬空的Promise的错误漏了处理,才导致测试报未捕获。
正确断言写法
  1. 所有异步逻辑的断言都要用await expect(promise).resolves/rejects.xxx的形式,不要用包一层async函数配toThrow()的写法测异步代码,那个写法只适合同步逻辑。
  2. 测试重复邮箱场景的用例改成下面这样,确保每一步异步操作都被等待:
it('Same email', async() => {                                                                
    // 正确等待第一次合法插入完成,不要留悬空Promise
    await expect(userRepo.save(userComplete)).resolves.toBeTruthy();                                                                    
                                                                                                     
    // 校验重复键错误,推荐匹配错误码和索引字段,比硬编码整段错误信息稳定,不会因为驱动版本升级挂掉
    await expect(userRepo.save(userUniqueEmail)).rejects.toMatchObject({
        name: 'MongoServerError',
        code: 11000,
        keyValue: { email: "test@test.com" }
    });

    // 如果你就是要匹配错误信息片段,用正则匹配即可,不用写全整串
    // await expect(userRepo.save(userUniqueEmail)).rejects.toThrow(/E11000 duplicate key error/);
});
  1. 你第一个“合法用户创建”的测试用例原来的写法也是假通过,建议一并修正:
it('Can be created', async() => {                                                            
    // 直接await操作,如果抛错测试自然失败,也可以加resolves断言
    const createdUser = await userRepo.save(userComplete);
    expect(createdUser.email).toBe(userComplete.email);
});

额外注意:跑Mongoose相关测试前,最好在测试启动阶段等所有模型的索引创建完成再跑用例,避免索引还没建好就执行插入,导致唯一校验失效。可以在测试初始化文件里加await User.init()等索引建完。

内容的提问来源于stack exchange,提问作者lpares12

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 11:33:16