Vue оновлює DOM асинхронно: зміна стану ставить оновлення в чергу, і DOM змінюється в наступному «тіку». Тест, що перевіряє DOM одразу після зміни, бачить старий стан.
Два різні види очікування:
1. Оновлення DOM після зміни стану - await nextTick() або await на методах Test Utils:
await wrapper.get('button').trigger('click') // trigger повертає nextTick
expect(wrapper.text()).toContain('Відкрито')
wrapper.vm.open = true // пряма зміна стану
await nextTick()
trigger, setValue, setProps уже повертають Promise, що завершується після оновлення DOM - окремий nextTick після них не потрібен.
2. Завершення інших Promise (запити, таймери з Promise, асинхронні дії сторів) - flushPromises():
import { flushPromises, mount } from '@vue/test-utils'
vi.spyOn(api, 'fetchOrders').mockResolvedValue([{ id: 1, total: 100 }])
const wrapper = mount(OrdersList)
await flushPromises() // дочекатися всіх розв'язаних Promise і рендеру
expect(wrapper.findAll('[data-test="order"]')).toHaveLength(1)
flushPromises чекає, поки виконаються всі вже розв'язані Promise в черзі, - зокрема ланцюжки await усередині компонента після замоканого запиту.
Таймери (setTimeout, debounce) - фальшивий час Vitest:
vi.useFakeTimers()
await wrapper.get('input').setValue('lar')
vi.advanceTimersByTime(300) // «промотати» debounce
await flushPromises()
vi.useRealTimers()
Асинхронний setup (top-level await у <script setup>) вимагає <Suspense> - у тесті компонент обгортають у Suspense-обгортку і чекають flushPromises().
Типові помилки:
- тест «зелений» без очікувань: перевірка відбувається до завершення запиту, але випадково збігається з початковим станом (наприклад, «список порожній»);
flushPromisesзамість мокування: справжній мережевий запит він не дочекається - запити в тестах мають бути замокані;- реальні таймери роблять тест повільним і нестабільним - фальшиві таймери надійніші.
Докладніше в документації: Vue Test Utils: асинхронна поведінка