Обработка Promise в тестах

При тестировании Vue-компонентов часто возникает необходимость проверять асинхронное поведение, например, загрузку данных с API или выполнение внутренних промисов. Vue Test Utils предоставляет средства для корректной работы с асинхронностью, позволяя уверенно управлять состоянием компонента после выполнения промисов.


Основы асинхронного тестирования

Vue Test Utils использует реальную очередь обновлений Vue, поэтому изменения в реактивных данных не применяются мгновенно. Это особенно важно при работе с промисами:

import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

test('обновление данных после промиса', async () => {
  const wrapper = mount(MyComponent)
  await wrapper.vm.fetchData() // метод, возвращающий промис
  await wrapper.vm.$nextTick()
  expect(wrapper.text()).toContain('Данные загружены')
})

Ключевой момент: await wrapper.vm.$nextTick() гарантирует, что Vue успел обновить DOM после изменения реактивного состояния.


Использование flushPromises

Для удобного ожидания всех активных промисов можно применять утилиту flush-promises:

import flushPromises from 'flush-promises'

test('загрузка данных через API', async () => {
  const wrapper = mount(MyComponent)
  wrapper.vm.loadData() // вызывает промис
  await flushPromises()
  expect(wrapper.find('.data-item').exists()).toBe(true)
})

flushPromises() позволяет дождаться завершения всех промисов в текущем цикле событий, исключая необходимость многократного вызова $nextTick.


Моки и имитация асинхронных функций

Для тестов удобно заменять реальные вызовы API моками:

jest.mock('@/api', () => ({
  fetchItems: jest.fn(() => Promise.resolve(['item1', 'item2']))
}))

import { fetchItems } from '@/api'

test('отрисовка списка после fetchItems', async () => {
  const wrapper = mount(MyComponent)
  await wrapper.vm.loadItems()
  await flushPromises()
  expect(wrapper.findAll('li')).toHaveLength(2)
  expect(fetchItems).toHaveBeenCalled()
})

Особенности:

  • jest.mock позволяет контролировать возвращаемые значения и исключить сетевые запросы.
  • Асинхронные методы компонента нужно обязательно обернуть в await или дождаться с помощью flushPromises.

Асинхронные события компонента

Компоненты могут эмитировать события после завершения промиса:

test('эмит события после завершения промиса', async () => {
  const wrapper = mount(MyComponent)
  wrapper.vm.saveData()
  await flushPromises()
  expect(wrapper.emitted('saved')).toBeTruthy()
})

Эмит событий можно проверять после полного завершения всех асинхронных действий, что предотвращает ложноположительные или неполные проверки.


Работа с таймерами и промисами

Если промис зависит от таймеров (setTimeout, setInterval), важно использовать Jest Fake Timers:

jest.useFakeTimers()

test('обновление данных через setTimeout', async () => {
  const wrapper = mount(MyComponent)
  wrapper.vm.delayedUpdate()
  jest.runAllTimers()
  await flushPromises()
  expect(wrapper.text()).toContain('Обновлено')
})

Примечание: jest.runAllTimers() синхронно завершает все таймеры, а flushPromises гарантирует обработку промисов, созданных внутри таймера.


Асинхронная проверка реактивных свойств

Vue Test Utils позволяет проверять реактивные данные после промисов:

test('реактивное свойство после промиса', async () => {
  const wrapper = mount(MyComponent)
  await wrapper.vm.fetchStatus()
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.status).toBe('loaded')
})

Рекомендация: всегда использовать комбинацию await и $nextTick или flushPromises, чтобы избежать состояния гонки между завершением промиса и обновлением DOM.


Частые ошибки и подводные камни

  1. Игнорирование $nextTick или flushPromises — приводит к проверкам до того, как DOM обновился.
  2. Смешение реальных и мокнутых API — создаёт нестабильные тесты.
  3. Неожиданные промисы внутри вычисляемых свойств или watch — требуют явного ожидания завершения всех промисов.

Итоговая практика

Для стабильного тестирования асинхронного поведения рекомендуется:

  • Всегда оборачивать вызовы методов, возвращающих промисы, в await.
  • Использовать flushPromises для ожидания всех активных промисов.
  • Применять $nextTick для синхронизации обновлений Vue.
  • Мокать сетевые вызовы и таймеры для предсказуемости тестов.

Эти подходы обеспечивают точную проверку состояния компонентов и корректное тестирование сложной логики асинхронных действий.