如何使用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的作用:通过mock
bcrypt.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
相关产品推荐
相关产品推荐

