vuejs / test-utils

Vue Test Utils for Vue 3
https://test-utils.vuejs.org
MIT License
1.04k stars 244 forks source link

Bug: Not possible to test components with async setup #2528

Closed ersi77 closed 4 days ago

ersi77 commented 4 days ago

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?

<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 { 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');
  });
});```
cexbrayat commented 4 days ago

Hi @ersi77

Reading through your example, I think this is similar to this discussion: https://github.com/vuejs/test-utils/discussions/1904

If that doesn't help, can you provide us a small repro online using https://stackblitz.com/github/vuejs/create-vue-templates/tree/main/typescript-vitest?file=src%2Fcomponents%2F__tests__%2FHelloWorld.spec.ts ?

It only takes a few minutes, and we'll be able to take a look

ersi77 commented 4 days ago

Hi @ersi77

Reading through your example, I think this is similar to this discussion: #1904

If that doesn't help, can you provide us a small repro online using https://stackblitz.com/github/vuejs/create-vue-templates/tree/main/typescript-vitest?file=src%2Fcomponents%2F__tests__%2FHelloWorld.spec.ts ?

It only takes a few minutes, and we'll be able to take a look

I have tried adding fakeTimers but it didnt help here is link with added test file and component simpleList.vue https://stackblitz.com/edit/github-32gn1n?file=src%2Fcomponents%2F__tests__%2FSimpleList.test.ts

cexbrayat commented 4 days ago

You forgot to advance the timer before flushing:

import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect, vi } from 'vitest';
import SimpleList from '../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(500);
    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');
  });
});