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

如何测试Mongoose pre-save钩子中的bcryptjs.hash?单元测试报错TypeError的排查

解决Mongoose模型测试中bcryptjs.hash Stub失败的问题

你遇到的这个错误本质上有两个核心原因:一是你的测试逻辑跳过了Mongoose的模型生命周期钩子,二是可能存在bcryptjs导入不一致的问题,咱们一步步拆解解决:

为什么之前的测试会失败?

  1. 错误Stub了User.create:你用sinon.stub(User, 'create').callsFake(() => {return 42})直接替换了create的实现,这等于完全绕过了Mongoose的模型逻辑——pre('save')钩子是在实例保存时触发的,而你自定义的create函数根本不会触发任何模型生命周期,自然不会调用bcryptjs.hash。
  2. bcryptjs引用不一致:错误提示Attempted to wrap undefined property hash as function,说明sinon找不到bcryptjs上的hash方法,大概率是你测试文件中导入bcryptjs的方式和User模型文件里的导入方式不匹配(比如模型用默认导入,测试用命名导入)。

修正后的测试代码

下面是能正确测试密码哈希逻辑的代码示例(假设用Mocha+Sinon+Chai栈):

import { expect } from 'chai';
import sinon from 'sinon';
import bcryptjs from 'bcryptjs';
import { User } from '../your-path-to-user-model';
import { arrayOfUsers } from '../your-path-to-test-data';

describe('User Model', () => {
  // 每次测试后恢复所有stub,避免污染其他测试用例
  afterEach(() => {
    sinon.restore();
  });

  it('should hash password before saving user', async () => {
    // 1. Stub bcryptjs.hash,模拟异步哈希返回值
    const mockHash = 'fake-hashed-password-123';
    const hashStub = sinon.stub(bcryptjs, 'hash').resolves(mockHash);

    // 2. 创建User实例并调用save,触发pre('save')钩子
    const user = new User({
      name: arrayOfUsers[0].name,
      email: arrayOfUsers[0].email,
      password: arrayOfUsers[0].password
    });
    await user.save();

    // 3. 验证哈希逻辑是否正确执行
    expect(hashStub.calledOnce).to.be.true;
    // 检查hash方法的参数是否正确(原始密码 + 盐值rounds=10)
    expect(hashStub.calledWith(arrayOfUsers[0].password, 10)).to.be.true;
    // 验证用户密码是否被替换为模拟的哈希值
    expect(user.password).to.equal(mockHash);
  });

  // 额外补充:测试isValidPassword方法的逻辑
  it('should correctly validate password with isValidPassword', async () => {
    const compareStub = sinon.stub(bcryptjs, 'compare').resolves(true);
    const user = new User({ password: 'existing-hashed-password' });
    
    const isValid = await user.isValidPassword('user-input-password');
    
    expect(compareStub.calledWith('user-input-password', 'existing-hashed-password')).to.be.true;
    expect(isValid).to.be.true;
  });
});

关键注意事项

  • 永远不要跳过模型生命周期:测试Mongoose的钩子逻辑,一定要通过创建实例并调用save()(或者用User.create但不Stub它)来触发钩子,而不是直接替换掉create的实现。
  • 保持依赖导入一致:确保测试文件和模型文件中导入bcryptjs的方式完全相同(比如都是import bcryptjs from 'bcryptjs',或者都是const bcryptjs = require('bcryptjs'))。
  • 清理Stub:用sinon.restore()在每个测试后恢复被Stub的方法,防止不同测试用例之间互相干扰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 12:57:35