Home > front end >  Vue3: Testing load state fails in async function inside 'onMounted'
Vue3: Testing load state fails in async function inside 'onMounted'

Time:05-10

I'd like to test that a loading placeholder is displayed, while data from the server is fetching inside onMounted hook. Here is the component I'm testing:

<template>
  <section >
    <div
      v-if="collections && Object.keys(collections).length"
      
      data-test="collectionCards"
    >
      <div 
        v-for="collection in collections"
        :key="collection.cardSetId"
        
      >
        <div >
          <p >4 days ago</p>
        </div>
        
        <div >
          <div>
            <div ></div>
            <div >
              <h3  data-test="collectionName">{{ collection.name }}</h3>
              <p >
                <span data-test="collectionLength">{{ collection.length }}</span> item(s)</p>
            </div>
            <div >
              <base-collapse data-test="collectionDescription">{{ collection.description }}</base-collapse>
            </div>
          </div>

          <div >
            <p >Author: <span >Kanstantsin Lazouski</span></p>
          </div>
        </div>

      </div>
    </div>
    <div v-else-if="isLoading" data-test="loadingCollections">Loading...</div>
    <div v-else  data-test="emptyCollectionWrapper">
      <EmptyCollection />
    </div>
  </section>
</template>

/*....*/
onMounted(async () => {
  isLoading.value = true;

  const collectionList = await getCollectionList(userId.value as string);

  try {
    collections.value = collectionList;
  } catch(err) {
    /* TODO: add more appropriate error handling */
    console.error(err);
  } finally {
    isLoading.value = false;
  }
})

But everything I tried was not successful. As far as I understand, we should firstly mount component and then call flushPromises in order to make Vue wait until promises inside onMounted will be resolved. So, between calling mount and flushPromises there should be a timespan when isLoading is set to true. The problem is that, when we log 'isLoading' inside the test, it will display true (which is correct), but actual HTML is different from what it is supposed to be (I receive EmptyCollection inside v-else). Here is my test that's not working though:

  it('renders "EmptyCollection" if "collectionList" is empty and "Loading..." while data is fetching', async () => {
    (getCollectionList as jest.Mock).mockResolvedValueOnce({});
    const wrapper = mount(MyCollections, wrapperOptions);


    expect(wrapper.find('[data-test="loadingCollections"]').exists()).toBe(true); // received: false
    await flushPromises();
    
    expect(wrapper.find('[data-test="collectionCards"]').exists()).toBe(false);
    expect(wrapper.find('[data-test="emptyCollectionWrapper"]').exists()).toBe(true);
  });

Thanks in advance.

CodePudding user response:

const wrapper = mount(MyCollections, wrapperOptions);
await wrapper.vm.$nextTick();
expect(wrapper.find('[data-test="loadingCollections"]').exists()).toBe(true);
await flushPromises();
expect(wrapper.find('[data-test="loadingCollections"]').exists()).toBe(false);

Hello! You need to wait until vue is updated the html. Then you need to wait until all promises are done, then you can check again. The reason for that is Vue updates html in ticks. At first your component mounts with default data. Then the onMounted hook is called, in which you're setting isLoading, which updates the DOM in the next tick. You need to await this DOM changes, then you can check the appearance of that element in your tests.

Then you have to wait until api call is ended. This can be done by awaiting flushPromises (the side effect of what is that will include next tick of DOM update)

  • Related