Состояние и реактивность composables

Vue Test Utils (VTU) предоставляет инструменты для тестирования компонентов Vue, включая работу с реактивным состоянием, которое часто хранится в composables. Понимание того, как тестировать состояние и реактивность, является критически важным для надёжного покрытия тестами логики приложения.


Реактивность и composables

Composables — это функции, возвращающие реактивные объекты, вычисляемые свойства и методы, которые инкапсулируют логику состояния. Основные инструменты Vue для работы с реактивностью:

  • ref() — создаёт реактивное примитивное значение.
  • reactive() — создаёт реактивный объект или массив.
  • computed() — создаёт вычисляемое свойство на основе реактивных данных.
  • watch() и watchEffect() — позволяют отслеживать изменения реактивных данных.

Пример простого composable для счётчика:

import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  const double = computed(() => count.value * 2);

  function increment() {
    count.value++;
  }

  function decrement() {
    count.value--;
  }

  return { count, double, increment, decrement };
}

Монтирование composables через компоненты

VTU не предоставляет прямого способа протестировать composable изолированно, поэтому часто composable оборачивают в тестовый компонент. Пример:

import { mount } from '@vue/test-utils';
import { defineComponent } from 'vue';
import { useCounter } from '@/composables/useCounter';

const TestComponent = defineComponent({
  template: '<div></div>',
  setup() {
    return useCounter();
  }
});

const wrapper = mount(TestComponent);
const { count, double, increment } = wrapper.vm;

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


Тестирование реактивного состояния

При тестировании важно учитывать реактивность Vue. Любое изменение реактивного значения должно проверяться после его обновления:

import { nextTick } from 'vue';

increment();
await nextTick();

expect(count.value).toBe(1);
expect(double.value).toBe(2);
  • nextTick() гарантирует, что все реактивные обновления завершены перед проверкой состояния.
  • Для массивов и объектов, изменяемых через reactive, изменения также требуют nextTick() для корректного отражения в тестах.

Тестирование computed и watch

Computed свойства вычисляются автоматически при изменении зависимостей. Проверка выглядит так:

count.value = 5;
await nextTick();
expect(double.value).toBe(10);

Watchers можно протестировать с помощью имитации изменения зависимых значений и проверки побочных эффектов:

import { watch } from 'vue';

let log = '';
watch(count, (newVal) => {
  log = `count changed to ${newVal}`;
});

count.value = 3;
await nextTick();
expect(log).toBe('count changed to 3');

Изоляция состояния

Для предотвращения влияния одного теста на другой, необходимо создавать новый экземпляр composable для каждого теста:

test('increment работает независимо', async () => {
  const wrapper = mount(TestComponent);
  const { count, increment } = wrapper.vm;

  increment();
  await nextTick();

  expect(count.value).toBe(1);
});

Использование beforeEach позволяет гарантировать чистое состояние:

let wrapper;

beforeEach(() => {
  wrapper = mount(TestComponent);
});

Работа с асинхронными операциями

Если composable содержит асинхронную логику, например fetch или таймеры, тесты должны учитывать промисы и ожидание реактивных обновлений:

async function useAsyncCounter() {
  const count = ref(0);

  async function incrementAsync() {
    await new Promise(resolve => setTimeout(resolve, 100));
    count.value++;
  }

  return { count, incrementAsync };
}

test('incrementAsync увеличивает count', async () => {
  const wrapper = mount(defineComponent({
    setup: useAsyncCounter,
    template: '<div></div>'
  }));

  await wrapper.vm.incrementAsync();
  await nextTick();

  expect(wrapper.vm.count.value).toBe(1);
});
  • await nextTick() обеспечивает корректное обновление реактивного состояния после асинхронных операций.
  • Можно комбинировать с jest timers или mock-функциями для ускорения тестирования.

Советы по надежному тестированию composables

  1. Тестировать только public API composable — не проверять внутренние детали реактивных объектов, если они не экспортируются.
  2. Использовать отдельные компоненты для монтирования — изоляция тестов предотвращает побочные эффекты.
  3. Всегда использовать nextTick после изменений — особенно для объектов и массивов, созданных через reactive.
  4. Комбинировать с mock-функциями для внешних зависимостей — HTTP-запросы, таймеры и локальное хранилище.
  5. Проверять computed и watch — они часто содержат важную бизнес-логику и могут быть источником ошибок.

Эти методы позволяют глубоко проверять реактивное состояние в composables, обеспечивая высокое качество тестов и предсказуемость поведения компонентов Vue.