vue.jsunit-testingvuejs3vitestvue-test-utils

Unit tests using vue/test-utils not working with component that uses async setup


Im trying to use tests on component that is rendered asynchronously. I have tried to create simple component with async title but the problem still persists. I have added suspense around the component, added await flushPromises(); and also await wrapper.vm.$nextTick(); but nothing helps. Any recommendations?

simpleList.vue component:

<template>
  <div>
    <h1 v-if="title">{{ title }}</h1>
    <ul>
      <li v-for="(item, index) in items" :key="index">{{ item }}</li>
    </ul>
    <!-- Slot to render custom content -->
    <slot name="customSlot"></slot>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  name: "SimpleList",
  props: {
    items: {
      type: Array,
      required: true,
    },
  },
  async setup() {
    const title = ref('');

    // Simulate async operation to fetch the title
    const fetchTitle = async () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          title.value = 'Async Loaded Title';
          resolve();
        }, 500); // Simulate async delay
      });
    };

    await fetchTitle();

    return {
      title,
    };
  },
};
</script>

test file:

import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import SimpleList from '@/components/SimpleList.vue';
import { defineComponent } from 'vue';

describe('SimpleList with async setup', () => {
  it('renders the title and items when wrapped in Suspense', async () => {
    const WrapperComponent = defineComponent({
      components: { SimpleList },
      template: `
        <Suspense>
          <template #default>
            <SimpleList :items="['Item 1', 'Item 2', 'Item 3']" />
          </template>
          <template #fallback>
            <div>Loading...</div>
          </template>
        </Suspense>
      `
    });

    const wrapper = mount(WrapperComponent);

    // Wait for async operations
    await flushPromises();
    await flushPromises();

    // Ensure Suspense resolves
    await wrapper.vm.$nextTick();

    // Test for the title rendered from async setup
    const title = wrapper.find('h1');
    expect(title.exists()).toBe(true);
    expect(title.text()).toBe('Async Title');

    // Test for the list items
    const listItems = wrapper.findAll('li');
    expect(listItems).toHaveLength(3);
    expect(listItems[0].text()).toBe('Item 1');
    expect(listItems[1].text()).toBe('Item 2');
    expect(listItems[2].text()).toBe('Item 3');
  });
});


Solution

  • Managed to fix it with adding useFakeTimers and then advancingTimersByTime. Fixed test looks like this:

    import { mount, flushPromises } from '@vue/test-utils';
    import { describe, it, expect, vi } from 'vitest';
    import SimpleList from '@/components/rs/RsAnalysis/ImageAnalysis/SimpleList.vue';
    import { defineComponent } from 'vue';
    
    describe('SimpleList with async setup', () => {
      it('renders the title and items when wrapped in Suspense', async () => {
        vi.useFakeTimers();
        const WrapperComponent = defineComponent({
          components: { SimpleList },
          template: `
            <Suspense>
              <template #default>
                <SimpleList :items="['Item 1', 'Item 2', 'Item 3']" />
              </template>
              <template #fallback>
                <div>Loading...</div>
              </template>
            </Suspense>
          `,
        });
    
        const wrapper = mount(WrapperComponent);
    
        // Wait for async operations
        vi.advanceTimersByTime(1500);
        await flushPromises();
    
        // Test for the title rendered from async setup
        const title = wrapper.find('h1');
        expect(title.exists()).toBe(true);
        expect(title.text()).toBe('Async Loaded Title');
    
        // Test for the list items
        const listItems = wrapper.findAll('li');
        expect(listItems).toHaveLength(3);
        expect(listItems[0].text()).toBe('Item 1');
        expect(listItems[1].text()).toBe('Item 2');
        expect(listItems[2].text()).toBe('Item 3');
      });
    });

    Thanks for the answer in vue-test-utils git: https://github.com/vuejs/test-utils/issues/2528#issuecomment-2429405980