Таймауты и задержки

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


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

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

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

test('обновление текста после изменения пропса', async () => {
  const wrapper = mount(MyComponent, {
    props: { message: 'Привет' }
  });

  await wrapper.setProps({ message: 'Здравствуйте' });

  expect(wrapper.text()).toContain('Здравствуйте');
});

Использование await wrapper.setProps() гарантирует, что Vue завершил внутренние обновления DOM перед проверкой.


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

Функция nextTick из Vue позволяет дождаться завершения всех асинхронных реактивных обновлений:

import { nextTick } from 'vue';

wrapper.vm.value = 'новое значение';
await nextTick();

expect(wrapper.text()).toContain('новое значение');

nextTick особенно полезен при тестировании реактивных вычисляемых свойств или наблюдателей (watch), где обновление DOM происходит после завершения всех реакций Vue.


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

Компоненты часто используют setTimeout или setInterval для отложенных действий. В тестах важно контролировать время, чтобы тесты выполнялись быстро и предсказуемо. Для этого используется Jest Fake Timers:

jest.useFakeTimers();

test('отложенное изменение текста', () => {
  const wrapper = mount(MyComponent);

  wrapper.vm.startTimer(); // внутри компонента setTimeout изменяет значение через 1000 мс

  jest.advanceTimersByTime(1000); // симулируем проход времени

  expect(wrapper.text()).toContain('Время вышло');
});

Fake Timers позволяют полностью управлять таймерами и избегать настоящих задержек в тестах.


Асинхронные методы и промисы

Если компонент вызывает асинхронные методы, возвращающие промисы, важно дождаться их выполнения перед проверкой состояния:

test('обработка асинхронного запроса', async () => {
  const wrapper = mount(MyComponent);
  
  await wrapper.vm.fetchData(); // метод возвращает промис
  await nextTick(); // убедиться, что DOM обновлен после изменения данных

  expect(wrapper.text()).toContain('Данные загружены');
});

Комбинация await и nextTick гарантирует, что промис завершен, а Vue успел обновить DOM.


Таймауты в асинхронных тестах Jest

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

test('долгий асинхронный процесс', async () => {
  const wrapper = mount(MyComponent);

  await new Promise(resolve => setTimeout(resolve, 5000));

  expect(wrapper.text()).toContain('Завершено');
}, 10000); // таймаут теста 10 секунд

Без увеличенного таймаута тест может быть прерван Jest по умолчанию (5 секунд).


Рекомендации по синхронизации тестов

  1. Избегать реальных задержек. Использование setTimeout в тестах делает их медленными. Fake Timers или мокированные промисы позволяют ускорить выполнение.
  2. Дождаться реактивных обновлений. Любое изменение данных должно сопровождаться await nextTick() или соответствующим методом Vue Test Utils (setProps, setValue).
  3. Контролировать таймауты Jest. Для длительных асинхронных процессов устанавливать таймаут явно.
  4. Комбинировать fake timers и nextTick. Иногда необходимо одновременно симулировать таймер и дождаться обновления DOM:
jest.useFakeTimers();

wrapper.vm.delayedUpdate();
jest.runAllTimers();
await nextTick();

expect(wrapper.text()).toContain('Обновлено');
  1. Асинхронные события пользователя. При тестировании событий, инициирующих промисы или таймеры, порядок await и управления таймерами критичен для точной проверки состояния компонента.

Особенности Vue 3

В Vue 3 внутренние реакции и watchEffect выполняются асинхронно в микротасках. Использование flushPromises помогает дождаться всех промисов:

import flushPromises from 'flush-promises';

wrapper.vm.loadData();
await flushPromises();

expect(wrapper.text()).toContain('Данные готовы');

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


Итоговая структура теста с задержками

Эффективный подход при работе с таймаутами и асинхронными процессами включает:

  1. Монтирование компонента.
  2. Инициализацию асинхронного процесса (setTimeout, промис).
  3. Управление таймерами или ожидание завершения промисов (jest.runAllTimers(), flushPromises(), await nextTick()).
  4. Проверку DOM или состояния компонента.

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