如何在Karma/Jasmine中测试普通函数内Promise抛出的错误?
解决异步Promise中抛出错误的单元测试问题
首先要先指出一个你可能忽略的细节:你的AuthHelperMock里的sendSignInLink方法条件逻辑有误!当前的if (email != undefined || email != "")会导致空字符串也会进入resolve分支(因为空字符串不等于undefined,条件始终为true),永远不会触发reject。你需要把逻辑改成&&才能正确判断有效邮箱:
public sendSignInLink(email: String): Promise<any> { return new Promise((resolve, reject) => { if (email !== undefined && email !== "") resolve(1); else reject("Invalid email"); }); }
回到你的核心问题:因为doLogin里的错误是在Promise的catch回调中异步抛出的,同步的expect(() => component.doLogin()).toThrowError会在微任务执行前就结束,自然抓不到错误。下面是两种不需要修改原doLogin函数的测试方案:
方案1:使用Angular的fakeAsync + tick 捕获全局未处理Promise拒绝
利用Angular测试工具的fakeAsync可以模拟异步时序,tick()会触发所有待处理的微任务,再结合监听全局的unhandledrejection事件来捕获抛出的错误:
import { fakeAsync, tick } from '@angular/core/testing'; it('should throw EmailSignInLinkError on invalid email', fakeAsync(() => { component.email = ""; let caughtError: Error | undefined; // 临时替换全局未处理Promise拒绝的处理函数 const originalUnhandledRejection = window.onunhandledrejection; window.onunhandledrejection = (event) => { caughtError = event.reason as Error; event.preventDefault(); // 阻止测试因未捕获错误而失败 }; component.doLogin(); tick(); // 触发所有微任务,执行catch里的throw逻辑 // 验证错误类型和信息 expect(caughtError).toBeInstanceOf(EmailSignInLinkError); expect(caughtError?.message).toBe("invalid Mail"); // 恢复原来的处理函数,避免影响其他测试 window.onunhandledrejection = originalUnhandledRejection; }));
方案2:修改Mock跟踪Promise,结合async/await
给你的AuthHelperMock添加一个属性来跟踪sendSignInLink返回的Promise,这样测试代码可以直接等待这个Promise完成,再捕获异步抛出的错误:
export class AuthHelperMock { public lastSignInPromise: Promise<any> | undefined; public sendSignInLink(email: String): Promise<any> { this.lastSignInPromise = new Promise((resolve, reject) => { if (email !== undefined && email !== "") resolve(1); else reject("Invalid email"); }); return this.lastSignInPromise; } }
然后在测试中:
it('should throw EmailSignInLinkError on invalid email', async () => { component.email = ""; const authMock = TestBed.inject(AuthHelper) as AuthHelperMock; let caughtError: Error | undefined; // 监听未处理的Promise拒绝 const originalUnhandledRejection = window.onunhandledrejection; window.onunhandledrejection = (event) => { caughtError = event.reason as Error; event.preventDefault(); window.onunhandledrejection = originalUnhandledRejection; }; component.doLogin(); // 等待sendSignInLink的Promise完成 await authMock.lastSignInPromise; // 触发微任务执行catch里的throw await Promise.resolve(); expect(caughtError).toBeInstanceOf(EmailSignInLinkError); expect(caughtError?.message).toBe("invalid Mail"); });
这两种方案都不需要修改原doLogin函数的结构,就能正确捕获异步抛出的错误。
内容的提问来源于stack exchange,提问作者Benjamin Schröder
相关产品推荐
相关产品推荐

