如何用Karma-Jasmine测试含async/await的点击异步回调函数?
解决Lit-Element按钮异步回调的Karma-Jasmine测试问题
我之前在测试带IndexedDB异步操作的Lit-Element组件时,也踩过完全一样的坑!核心问题就是IndexedDB的异步操作属于宏任务,而Jasmine默认的异步处理(比如done回调)如果没处理好宏任务的等待,就会出现测试逻辑跑在真实数据更新之前的情况。给你几个亲测有效的解决方案:
1. 直接等待按钮回调的异步逻辑完成
如果你的组件把点击回调方法(比如handleSave)暴露成了实例方法,那最简单的方式就是在模拟点击后,直接await这个方法的执行:
it('should update restaurant data after button click', async () => { const component = fixture.componentInstance; const saveButton = fixture.debugElement.query('#save-btn'); // 模拟点击按钮 saveButton.click(); // 等待按钮回调里的所有异步操作完成 await component.handleSave(); // 现在再去获取并断言IndexedDB的数据 const updatedRestaurant = await RestaurantDB.getRestaurant(123); expect(updatedRestaurant.name).toBe('Updated Name'); });
如果回调方法是私有的,没法直接调用,也可以通过等待宏任务队列清空来间接等待:
await new Promise(resolve => setTimeout(resolve, 0));
这行代码会让测试逻辑等待当前所有宏任务(包括IndexedDB的操作)执行完毕。
2. Mock IndexedDB方法,消除真实异步延迟
既然测试的核心是验证按钮点击后是否正确触发了PutRestaurant方法,而不是测试IndexedDB本身,那直接Mock这个方法会更可靠,还能加快测试速度:
beforeEach(() => { // Mock PutRestaurant,让它立即返回resolved的Promise spyOn(RestaurantDB, 'PutRestaurant').and.returnValue(Promise.resolve()); // 同时Mock getRestaurant,让它返回我们预期的更新后数据 spyOn(RestaurantDB, 'getRestaurant').and.returnValue(Promise.resolve({ id: 123, name: 'Updated Name' })); }); it('should trigger PutRestaurant and update data', async () => { const saveButton = fixture.debugElement.query('#save-btn'); saveButton.click(); // 等待组件的异步逻辑处理完成 await fixture.whenStable(); // Lit-Element的fixture提供的方法,等待组件更新 // 断言方法被正确调用 expect(RestaurantDB.PutRestaurant).toHaveBeenCalledWith( jasmine.objectContaining({ id: 123 }) ); // 断言数据符合预期 const restaurant = await RestaurantDB.getRestaurant(123); expect(restaurant.name).toBe('Updated Name'); });
这种方式完全避开了真实IndexedDB的异步延迟,测试逻辑和组件的异步回调能同步执行。
3. 轮询检查数据状态,直到更新完成
如果必须测试真实的IndexedDB操作(比如要验证数据确实被持久化了),可以写一个轮询函数,定期检查数据是否更新,直到满足条件或超时:
it('should persist updated restaurant data to IndexedDB', (done) => { const saveButton = fixture.debugElement.query('#save-btn'); saveButton.click(); // 定义轮询检查的函数 const checkUpdatedData = async () => { const restaurant = await RestaurantDB.getRestaurant(123); if (restaurant && restaurant.name === 'Updated Name') { expect(restaurant.name).toBe('Updated Name'); done(); // 数据更新完成,结束测试 } else { // 100ms后再检查一次 setTimeout(checkUpdatedData, 100); } }; checkUpdatedData(); }, 5000); // 设置测试超时时间为5秒,避免无限等待
为什么之前的done或spyOn没生效?
- 用
done的时候,可能你没有等待按钮回调里的await PutRestaurant()完成就调用了done(),导致测试提前结束; spyOn如果没有返回Promise.resolve(),而是返回了同步值,那组件里的await会立即跳过,而真实的IndexedDB操作还在后台跑,断言自然会失败。
内容的提问来源于stack exchange,提问作者Dapa
相关产品推荐
相关产品推荐

