flushPromises для флюша всех Promise

При тестировании компонентов Vue часто возникает ситуация, когда внутри компонента выполняются асинхронные операции: HTTP-запросы, таймеры, события с nextTick, вызовы API или любые Promise. Для корректной проверки состояния компонента после завершения этих операций необходим механизм ожидания завершения всех промисов. В таких случаях используется flushPromises.

flushPromises — это утилита, которая позволяет «прокинуть» выполнение всех ожидающих промисов до следующего микротаска JavaScript, обеспечивая стабильное состояние компонента для проверок.


Принцип работы

JavaScript обрабатывает асинхронные задачи через микротаски и макротаски. Промисы попадают в очередь микротасков и выполняются после завершения текущего стека вызовов. В тестах Vue компоненты могут менять своё состояние после завершения промисов, поэтому напрямую проверять DOM после вызова асинхронной функции некорректно — состояние компонента ещё не обновлено.

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

Простейшая реализация:

export default function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

Или вариант с использованием Promise и then:

export default function flushPromises() {
  return new Promise(resolve => setTimeout(resolve, 0));
}

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


Использование в Vue Test Utils

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

jest.mock('axios');

test('асинхронный рендер после API-запроса', async () => {
  axios.get.mockResolvedValue({ data: { message: 'Привет' } });

  const wrapper = mount(MyComponent);

  // Компонент отправляет запрос сразу после монтирования
  await flushPromises(); // ждём завершения всех промисов

  // После flushPromises компонент обновился
  expect(wrapper.text()).toContain('Привет');
});

Ключевой момент: без flushPromises проверка может произойти до завершения промисов, и тест будет падать даже при правильной логике компонента.


Совмещение с nextTick

Vue обновляет DOM асинхронно после изменения реактивных данных. Иногда достаточно await wrapper.vm.$nextTick(), но если есть цепочки промисов или внешние асинхронные вызовы, одного $nextTick недостаточно.

await flushPromises();
await wrapper.vm.$nextTick();

Последовательность:

  1. flushPromises завершает все промисы.
  2. $nextTick обновляет DOM после изменения состояния.

Это гарантирует точность тестов при сложных асинхронных сценариях.


Примеры сложных сценариев

1. Цепочка промисов

const wrapper = mount(MyComponent);

wrapper.vm.loadData(); // возвращает Promise с цепочкой then

await flushPromises();

expect(wrapper.find('.data').text()).toBe('Данные загружены');

Цепочки промисов, например axios.get().then(...).then(...), корректно обрабатываются после вызова flushPromises.

2. Асинхронные события и таймеры

Если внутри компонента используются таймеры (setTimeout) вместе с промисами:

jest.useFakeTimers();

wrapper.vm.delayedAction(); // вызывает setTimeout и промис внутри

jest.runAllTimers();
await flushPromises();

expect(wrapper.vm.status).toBe('готово');

Сначала выполняются таймеры, затем flushPromises обеспечивает завершение всех промисов.


Важные замечания

  • flushPromises не отменяет промисы, а лишь ожидает их завершения.
  • Для Vue 3 с Composition API flushPromises особенно полезен при тестировании реактивных состояний и watchEffect.
  • Необходимо различать макротаски и микротаски: flushPromises работает только с микротасками (Promise). Таймеры и setInterval требуют отдельного контроля через jest timers.
  • В больших тестовых сценариях flushPromises повышает стабильность тестов и предотвращает ложные падения из-за асинхронного поведения.

Интеграция с Jest и Vue Test Utils

flushPromises легко интегрируется в любой тестовый файл:

beforeEach(() => {
  jest.clearAllMocks();
});

test('проверка нескольких асинхронных действий', async () => {
  axios.get.mockResolvedValueOnce({ data: { value: 1 } });
  axios.get.mockResolvedValueOnce({ data: { value: 2 } });

  const wrapper = mount(MyComponent);
  
  await flushPromises();

  expect(wrapper.find('.first').text()).toBe('1');
  expect(wrapper.find('.second').text()).toBe('2');
});

Такой подход позволяет последовательно тестировать несколько асинхронных действий без конфликтов и задержек.


flushPromises становится незаменимым инструментом при работе с Vue Test Utils, обеспечивая предсказуемое и корректное тестирование компонентов с любыми асинхронными операциями.