简体   繁体   English

如何使用 karma-jasmine 在 function 中使用具有异步等待的单击处理程序自动测试 function?

[英]How to automate testing function with click handler that have async await inside the function using karma-jasmine?

So i'm trying to testing on my button that run the function asynchronously.所以我正在尝试测试异步运行 function 的按钮。 this is my button logic looks like.这是我的按钮逻辑。

    // Function below will run when user click the button
    this._pageModule.favoriteButtonCallback = async () => {
      try {
        // I want to expect run after this await below is done
        await this._favoriteRestaurants.PutRestaurant(this._restaurant);
        console.log(
          'console log in button',
          await this._favoriteRestaurants.GetAllRestaurant(),
        );
        this._renderButton();
        return Promise.resolve(
          `Success add ${this._restaurant.name} to favorite!`,
        );
      } catch (err) {
        this._renderButton();
        return Promise.reject(
          new Error(
            `Failed add ${this._restaurant.name} to favorite! Error: ${err}`,
          ).message,
        );
      }
    };

and this is my test这是我的测试

fit('should be able to add the restaurant to favorite', async () => {
    expect((await RestaurantIdb.GetAllRestaurant()).length).toEqual(0);

    // spyOn(RestaurantIdb, 'PutRestaurant');

    document.body.innerHTML = `<detail-module></detail-module>
    <modal-element></modal-element>`;

    const pageModule = document.querySelector('detail-module');

    await FavoriteButtonInitiator.init({
      pageModule,
      restaurant,
      favoriteRestaurants: RestaurantIdb,
    });

    pageModule.restaurantDetail = restaurant;

    await pageModule.updateComplete;

    const favoriteButton = pageModule.shadowRoot
      .querySelector('[aria-label="favorite this restaurant"]')
      .shadowRoot.querySelector('button');

    // 1. Simulate user click the button
    favoriteButton.dispatchEvent(new Event('click'));

    // expect(RestaurantIdb.PutRestaurant).toHaveBeenCalled();

    const restaurants = await RestaurantIdb.GetAllRestaurant();
    console.log('console log from test', restaurants);
    expect(restaurants).toEqual([restaurant]);
  });

i'm using lit-element, simply it similar with react, i have custom element <define-module> with button inside.我正在使用 lit-element,只是它与 React 类似,我有自定义元素 <define-module>,里面有按钮。 then i give the required properties to it, then it will render.然后我给它所需的属性,然后它就会呈现。

This is my test log Test log这是我的测试日志测试日志

as you can see the console log from the test ran before the console log that i put in the button.如您所见,测试中的控制台日志在我放入按钮的控制台日志之前运行。 and it is empty array.它是空数组。

what i want is when click event dispatched.我想要的是何时发送点击事件。 the next line in the test wait until the asynchronous function in the button done, how do i make it possible?测试中的下一行等待按钮中的异步 function 完成,我怎样才能让它成为可能?

What have i done: i have tried to console log them.我做了什么:我试图通过控制台记录它们。 i have tried to using done in jasmine, but it doesn't work since i using async/await in the test.我曾尝试在 jasmine 中使用 done,但它不起作用,因为我在测试中使用了 async/await。 I have tried use spyOn, but i don't really understand how to spy indexedDb我试过使用 spyOn,但我真的不明白如何监视 indexedDb

UPDATE更新

So i have found what caused problem, here i have simplified my code.所以我找到了导致问题的原因,在这里我简化了我的代码。

/* eslint-disable */
import { openDB } from 'idb';
import { CONFIG } from '../src/scripts/globals';

const { DATABASE_NAME, DATABASE_VERSION, OBJECT_STORE_NAME } = CONFIG;

const dbPromise = openDB(DATABASE_NAME, DATABASE_VERSION, {
  upgrade(database) {
    database.createObjectStore(OBJECT_STORE_NAME, { keyPath: 'id' });
  },
});

const RestaurantIdb = {
  async GetRestaurant(id) {
    return (await dbPromise).get(OBJECT_STORE_NAME, id);
  },
  async GetAllRestaurant() {
    return (await dbPromise).getAll(OBJECT_STORE_NAME);
  },
  async PutRestaurant(restaurant) {
    if (await this.GetRestaurant(restaurant.id)) {
      return Promise.reject(
        new Error('This restauant is already in your favorite!').message,
      );
    }
    return (await dbPromise).put(OBJECT_STORE_NAME, restaurant);
  },
  async DeleteRestaurant(id) {
    if (await this.GetRestaurant(id)) {
      return (await dbPromise).delete(OBJECT_STORE_NAME, id);
    }
    return Promise.reject(
      new Error('This restauant is not in favorite!').message,
    );
  },
};

describe('Testing RestaurantIdb', () => {
  const removeAllRestaurant = async () => {
    const restaurants = await RestaurantIdb.GetAllRestaurant();

    for (const { id } of restaurants) {
      await RestaurantIdb.DeleteRestaurant(id);
    }
  };

  beforeEach(async () => {
    await removeAllRestaurant();
  });

  afterEach(async () => {
    await removeAllRestaurant();
  });

  it('should add restaurant', async () => {
    document.body.innerHTML = `<button></button>`;

    const button = document.querySelector('button');
    button.addEventListener('click', async () => {
      await RestaurantIdb.PutRestaurant({ id: 1 });
    });

    button.dispatchEvent(new Event('click'));

    setTimeout(async () => {
      const restaurants = await RestaurantIdb.GetAllRestaurant();
      console.log('console log in test', restaurants);

      expect(restaurants).toEqual([{ id: 1 }]);
    }, 0);
  });
});

And this is the result Test Result这是结果测试结果

I assume that IndexedDb takes times to put my restaurant data.我假设 IndexedDb 需要时间来放置我的餐厅数据。 and i still can't figure out how to fix it.我仍然不知道如何解决它。

If you were using Angular, you would have access to fixture.whenStable() , and fakeAsync and tick() which wait until promises are resolved before carrying forward with the test.如果您使用的是 Angular,您将可以访问 fixture.whenStable fixture.whenStable()以及fakeAsync and tick() ,它们会等到 promises 被解决后再继续测试。

In this scenario, I would try wrapping what you have in the test in a setTimeout在这种情况下,我会尝试将测试中的内容包装在setTimeout

fit('should be able to add the restaurant to favorite', async () => {
    expect((await RestaurantIdb.GetAllRestaurant()).length).toEqual(0);

    // spyOn(RestaurantIdb, 'PutRestaurant');

    document.body.innerHTML = `<detail-module></detail-module>
    <modal-element></modal-element>`;

    const pageModule = document.querySelector('detail-module');

    await FavoriteButtonInitiator.init({
      pageModule,
      restaurant,
      favoriteRestaurants: RestaurantIdb,
    });

    pageModule.restaurantDetail = restaurant;

    await pageModule.updateComplete;

    const favoriteButton = pageModule.shadowRoot
      .querySelector('[aria-label="favorite this restaurant"]')
      .shadowRoot.querySelector('button');

    // 1. Simulate user click the button
    favoriteButton.dispatchEvent(new Event('click'));

    // expect(RestaurantIdb.PutRestaurant).toHaveBeenCalled();
    setTimeout(() => {
     const restaurants = await RestaurantIdb.GetAllRestaurant();
     console.log('console log from test', restaurants);
     expect(restaurants).toEqual([restaurant]);
    }, 0);
  });

The things in the setTimeout should hopefully happen after the asynchronous task of the button click since promises are microtasks and setTimeout is a macrotask and microtasks have higher priority than macrotasks. setTimeout中的事情应该在按钮单击的异步任务之后发生,因为 promises 是微任务,而setTimeout是一个宏任务,微任务比宏任务具有更高的优先级。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM