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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 09:55:14