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

如何使用Chai处理Promise拒绝并测试异步用户验证函数?

如何用Chai测试异步用户验证函数的Promise拒绝情况?

首先,先理清楚你的需求:你有一个异步的用户验证函数,现在需要用Chai完成它的完整测试,特别是处理Promise拒绝的场景。我会一步步给你讲清楚怎么实现。

先回顾下你的现有代码:

你的异步验证函数

validate.user = async (user, password) => { 
  const matches = await bcrypt.compare(password, user.password); 
  if (matches) return user; 
  if (!matches) { 
    return validate.logAndThrow('User password does not match'); 
  } 
  return validate.logAndThrow('Error in bcrypt compare'); 
};

你的测试片段(未完成)

chai.use(sinonChai); 
const { expect } = chai; 
describe('#user', () => { 
  it('should throw if user is undefined'

核心思路:Chai处理异步Promise的两种方式

Chai处理异步代码的测试,最常用的是async/await结合断言,或者配合chai-as-promised插件让语法更简洁。另外,为了覆盖所有代码分支,我们还可以用Sinon来mockbcrypt.compare的返回值,避免依赖真实的哈希计算。

1. 先准备好依赖(如果需要)

如果你想用更简洁的Promise断言,先安装chai-as-promised:

npm install chai-as-promised --save-dev

然后在测试文件里引入:

chai.use(sinonChai);
chai.use(require('chai-as-promised')); // 新增这行
const { expect } = chai;

2. 编写完整的测试用例

下面是覆盖所有分支的完整测试代码,我会逐个解释每个用例:

const sinon = require('sinon');
const bcrypt = require('bcrypt');

chai.use(sinonChai);
chai.use(require('chai-as-promised'));
const { expect } = chai;

describe('#user', () => {
  let bcryptCompareStub;

  // 每个测试前mock bcrypt.compare
  beforeEach(() => {
    bcryptCompareStub = sinon.stub(bcrypt, 'compare');
  });

  // 每个测试后恢复原方法
  afterEach(() => {
    bcryptCompareStub.restore();
  });

  // 测试1:用户为undefined时,访问user.password会抛出TypeError
  it('should reject with TypeError when user is undefined', async () => {
    await expect(validate.user(undefined, 'any-password')).to.be.rejectedWith(TypeError);
  });

  // 测试2:密码匹配时,返回用户对象
  it('should return the user when password matches', async () => {
    const mockUser = { id: 1, password: 'fake-hash' };
    // 模拟bcrypt.compare返回true
    bcryptCompareStub.resolves(true);

    const result = await validate.user(mockUser, 'correct-password');
    expect(result).to.deep.equal(mockUser);
    // 验证bcrypt.compare被正确调用
    expect(bcryptCompareStub.calledOnceWith('correct-password', mockUser.password)).to.be.true;
  });

  // 测试3:密码不匹配时,拒绝并返回指定错误信息
  it('should reject with "User password does not match" when password is incorrect', async () => {
    const mockUser = { password: 'fake-hash' };
    // 模拟bcrypt.compare返回false
    bcryptCompareStub.resolves(false);

    await expect(validate.user(mockUser, 'wrong-password')).to.be.rejectedWith('User password does not match');
    expect(bcryptCompareStub.calledOnceWith('wrong-password', mockUser.password)).to.be.true;
  });

  // 测试4:bcrypt.compare调用失败时,返回对应错误
  it('should reject with "Error in bcrypt compare" when bcrypt fails', async () => {
    const mockUser = { password: 'fake-hash' };
    // 模拟bcrypt.compare抛出错误
    bcryptCompareStub.rejects(new Error('bcrypt error'));

    await expect(validate.user(mockUser, 'any-password')).to.be.rejectedWith('Error in bcrypt compare');
    expect(bcryptCompareStub.calledOnce).to.be.true;
  });
});

关键细节解释

  • async/await的使用:所有测试用例都标记为async,用await等待异步函数的结果,测试框架会自动处理Promise的状态。
  • chai-as-promised的便捷性:expect(promise).to.be.rejectedWith()可以直接断言Promise被拒绝,并且匹配错误信息或类型,不用写繁琐的try/catch。
  • Sinon Stub的作用:通过mockbcrypt.compare的返回值,我们可以精准控制代码走到哪个分支(匹配、不匹配、调用失败),确保所有代码路径都被测试覆盖,同时避免真实哈希计算带来的性能损耗。
  • 不用chai-as-promised的替代方案:如果不想装额外插件,可以用try/catch手动捕获错误:
    it('should reject when user is undefined', async () => {
      try {
        await validate.user(undefined, 'any-password');
        // 如果走到这里,说明测试失败
        expect.fail('Expected promise to be rejected');
      } catch (err) {
        expect(err).to.be.an.instanceOf(TypeError);
      }
    });
    

这样你的异步验证函数的所有场景就都覆盖到了,测试也能稳定运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:29:35