Функция useX() в компонентах

Функции вида useX() в Vue представляют собой композиционные функции (composables), которые позволяют выносить повторяемую логику из компонентов в отдельные модули. Они тесно интегрируются с Composition API и предоставляют удобный способ организации состояния, реактивных данных и методов. Тестирование таких функций требует понимания их реактивного поведения и взаимодействия с компонентами.


Создание и структура useX()

Типичная структура composable-функции:

import { ref, computed, watch } from 'vue';

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

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

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

  watch(count, (newValue) => {
    console.log('Count changed:', newValue);
  });

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

Ключевые моменты:

  • ref и reactive используются для реактивного состояния.
  • computed создаёт производные значения без дублирования логики.
  • watch позволяет отслеживать изменения и выполнять побочные эффекты.
  • Функция возвращает объекты и методы, которыми может пользоваться компонент.

Интеграция useX() в компонент

Composable-функции подключаются в компонент через setup():

<template>
  <div>
    <p>{{ count }}</p>
    <p>Double: {{ double }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script>
import { useCounter } from './useCounter';

export default {
  setup() {
    const { count, increment, decrement, double } = useCounter(5);

    return {
      count,
      increment,
      decrement,
      double
    };
  }
};
</script>

Особенности использования:

  • Возвращаемые ref и computed связываются с шаблоном автоматически.
  • Методы composable можно вызывать прямо в шаблоне или внутри других функций компонента.
  • setup() обеспечивает локальное состояние, которое легко тестировать независимо от DOM.

Тестирование composable-функций с Vue Test Utils

Тестирование функций useX() обычно разделяется на два подхода:

  1. Тестирование отдельно от компонента Используется для проверки логики, реактивности и вычислений без рендера DOM.
import { useCounter } from '@/composables/useCounter';
import { nextTick } from 'vue';

describe('useCounter', () => {
  it('инициализирует с переданным значением', () => {
    const { count } = useCounter(10);
    expect(count.value).toBe(10);
  });

  it('увеличивает и уменьшает значение', () => {
    const { count, increment, decrement } = useCounter(1);

    increment();
    expect(count.value).toBe(2);

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

  it('вычисляет double корректно', () => {
    const { count, double } = useCounter(3);
    expect(double.value).toBe(6);

    count.value = 4;
    expect(double.value).toBe(8);
  });
});

Преимущества такого подхода:

  • Быстрые тесты без рендеринга.
  • Проверка чистой логики composable.
  • Отсутствие зависимости от Vue компонента или DOM.
  1. Тестирование через компонент с mount или shallowMount
import { mount } from '@vue/test-utils';
import CounterComponent from '@/components/CounterComponent.vue';

describe('CounterComponent', () => {
  it('корректно отображает значение и реагирует на кнопки', async () => {
    const wrapper = mount(CounterComponent);

    expect(wrapper.text()).toContain('5');
    await wrapper.find('button').trigger('click'); // кнопка increment
    expect(wrapper.text()).toContain('6');
  });
});

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

  • Проверяется интеграция composable с компонентом.
  • Можно отследить реакцию DOM на изменения реактивных данных.
  • Подходит для e2e-подобных сценариев в юнит-тестах.

Мокирование зависимостей

Composable-функции могут использовать сторонние сервисы или глобальные состояния. Для тестирования удобно мокировать такие зависимости:

import { useApi } from '@/composables/useApi';
jest.mock('@/composables/useApi');

useApi.mockReturnValue({
  fetchData: jest.fn(() => Promise.resolve('mocked data'))
});

Это позволяет изолировать логику composable и сосредоточиться на её поведении без реальных вызовов API.


Тонкости реактивности и nextTick

При тестировании composable с watch или изменяемыми ref иногда необходимо использовать await nextTick() для ожидания обновления реактивных значений:

import { nextTick } from 'vue';

it('отслеживает изменения count через watch', async () => {
  const { count } = useCounter(0);
  let observedValue;
  const stop = watch(count, (val) => {
    observedValue = val;
  });

  count.value = 5;
  await nextTick();

  expect(observedValue).toBe(5);
  stop();
});

Без nextTick наблюдатель может не успеть среагировать на изменение в тесте.


Практические рекомендации

  • Выносить повторяемую логику в composable для упрощения тестирования и поддержки.
  • Тестировать сначала отдельно от компонента, затем через компонент при необходимости интеграции.
  • Мокировать зависимости, чтобы избежать сетевых вызовов или глобальных эффектов.
  • Использовать nextTick, когда проверяется реактивное обновление с watch или вычисляемыми свойствами.
  • Делать composable чистыми функциями, минимизируя побочные эффекты и глобальные состояния.

Функции useX() создают модульный, легко тестируемый слой логики в Vue-компонентах, который значительно упрощает поддержку кода и написание юнит-тестов с Vue Test Utils. Их правильное использование обеспечивает изоляцию состояния, предсказуемость реактивности и удобство интеграции с компонентами.