What is the best way to automate the testing of functions with click handlers that contain async await statements using the karma-jasmine framework?

Trying to test my button that runs a function asynchronously. Here is the logic for my button:

    // Function below will run when user clicks the button
    this._pageModule.favoriteButtonCallback = async () => {
      try {
        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 here is my test:

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

    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');

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

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

I'm using lit-element which is similar to react. I have a custom element <define-module> with a button inside. After giving it the required properties, it will render.

This is the test log Test log

The issue is that the console log from the test runs before the console log in the button, showing an empty array. I want the next line in the test to wait until the asynchronous function in the button is done. How can I achieve this?

What I've tried: - Console logging - Using Jasmine's done method, but it doesn't work due to async/await in the test - Trying spyOn, but unsure about spying on IndexedDB

UPDATE

I found what caused the problem and 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 here is the result Test Result

IndexedDB takes time to put my restaurant data, and I am still figuring out how to fix it.

Answer №1

When utilizing Angular, options like fixture.whenStable() and fakeAsync and tick() are available to assist in waiting for promise resolutions before proceeding with a test.

In this particular case, I would suggest enclosing your test content within a 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 setTimeout block is expected to execute following the asynchronous task of the button click due to the higher priority of microtasks over macrotasks.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Experiments related to Databases

Before diving into my issue, I want to mention that I am a beginner in the field of testing. Here is the problem I am facing: I have developed a REST API using Express and Sequelize (MySQL), and now I need to write some tests for it. I have chosen to util ...

Update the dropdown menu based on the revised time

I need to create a PHP site and JavaScript function that changes a dropdown menu based on specific time intervals, such as 7:00-8:00 (id530), 13:00-15:00 (id1330), or 20:00-03:00 (id2130). For example, if the time is 7:31, the dropdown menu should display/ ...

Dropdown box with search functionality in twig

Here is the structure of my form: public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('Name') ->add('Occupation'); } This is what my edit.html.twig lo ...

`How to send information from a Vue component to the HTML view in a Laravel application`

I am facing an issue with passing data from a Vue component to a Blade file. I have tried creating props, but it did not work for me. Is there any way to pass an object to props in order to retrieve the necessary data? As a newbie in Laravel, I am looking ...

Utilizing Props in Vue.js to Access Data for v-model

After browsing online, I attempted to pass props to data in the following manner: Child Component: props: { idInput: { type: String, required: false }, nameInput: { type: String, required: false }, }, data() { return { id: this.idInput, na ...

Alert will only occur once the done function in jquery's .ajax method is executed

Something seems off and I'm struggling to understand the situation. I've used jQuery's .ajax() function to run a script that retrieves data from the database. The script is functioning properly and the data is being returned as expected: $. ...

The server is showing a discrepancy in date comparisons with MomentJS

I am struggling with grouping events by day in my application. While it works correctly in the development environment (Brazil), on the server (USA) the events that occur at the end of the day are being placed at the beginning of the next day. I suspect th ...

Add three rows without clicking, then click once to add one row at a time

Seeking guidance on how to defaultly display 3 rows after adding and removing rows, as well as applying the removal of a default set of 3 rows using JavaScript. Any valuable ideas are appreciated! Example needed:- https://i.sstatic.net/DF8Wn.png $(docum ...

looping through the multi-dimensional array using v-for

Interested in using v-for and I have encountered a scenario with an object structure as seen here: https://i.sstatic.net/wNguk.png Upon inspecting the dev console, the object looks similar to this: https://i.sstatic.net/jyqth.png My query is about how to ...

What could be the reason for the jquery click event not working?

When viewing default.aspx, you can click on the + symbol to increase the quantity and the - symbol to decrease the quantity. <div class="sp-quantity"> <div class="sp-minus fff"> ...

How can I display an array of data with a changing name using a FlatList in React Native?

How can I render a list of array data with a dynamic name in a FlatList using React Native? Below is the list of data that I would like to display in the FlatList: const movies = [ { '4W2JJ0CLbvfLJzBUHORVaz6sAGv2': [ { name: ...

Cross-Origin Resource Sharing (CORS) issue: The Access-Control-Allow-Headers in preflight response does not allow the Authorization request header field

I am currently attempting to send a request from one localhost port to another. Specifically, I am utilizing angularjs on the frontend and node on the backend. Given that this is a CORS request, in my node.js code, I have implemented the following: res.h ...

Comparison: Chrome extension - utilizing default pop-up vs injecting a div directly into the page

I find myself perplexed by the common practices used in popular Chrome extensions. I am currently working on creating my own Chrome extension and after completing a basic tutorial, I have set up a default popup page that appears when clicking the extensi ...

Discontinuing the fieldset tab interface feature from a Dexterity content type

I am looking to modify a condition to prevent the loading of certain javascript code when inserting an object of my content type. The current condition works only when editing the object: <?xml version="1.0"?> <object name="portal_javascripts"> ...

Setting the width of an image within an iframe: A step-by-step guide

Is there a way to adjust the width of an image within an iframe? Typically, if an image with high resolution is placed inside an iframe, the iframe becomes scrollable by default. ...

Choose the initial unordered list within a specific division through Jquery

In a div, there is a ul. Inside a li, there is another ul. The task is to select only the first ul inside the div using jQuery. The HTML markup: <div class="parent"> <div class="clearfix"> <div class="another-div"> <ul cl ...

What is the best way to activate CSS filters on VueJS once the project has been compiled?

While working on a Node server locally, my SVG filter functions properly. However, once I build the project and run it on a server, the filter stops working. This VueJS project is utilizing Webpack as its build tool. The process of building the app invol ...

Body section CSS selector

Can a CSS selector be included within the body section of an HTML document? Here's an example of my code (although it is not functioning as expected): <html> <head> </head> <body> <div style= "a[target=_blank] {backgroun ...

I am struggling to decide which attribute to use for implementing image swap on mouseover() and mouseout()

I have a problem using jQuery to switch between images when hovering on and off. Here's the code snippet I'm working with: HTML <img class="commentImg" src="images/comments.png" data-swap="images/comment_hover.png" alt=""> jQuery $(" ...

The automatic form completion feature in JavaScript failed to function

The last 2 rows starting from "request.done...." are not functioning correctly. Nothing happens when these lines of code run, even though everything else works perfectly Here is the script I am using: $(document).ready(function () { $('#retriev ...