Функции вида 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 связываются с
шаблоном автоматически.setup() обеспечивает локальное состояние, которое легко
тестировать независимо от DOM.Тестирование функций useX() обычно разделяется на два
подхода:
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);
});
});
Преимущества такого подхода:
mount или
shallowMountimport { 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-функции могут использовать сторонние сервисы или глобальные состояния. Для тестирования удобно мокировать такие зависимости:
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 наблюдатель может не успеть среагировать на
изменение в тесте.
nextTick, когда
проверяется реактивное обновление с watch или вычисляемыми
свойствами.Функции useX() создают модульный, легко тестируемый слой
логики в Vue-компонентах, который значительно упрощает поддержку кода и
написание юнит-тестов с Vue Test Utils. Их правильное использование
обеспечивает изоляцию состояния, предсказуемость реактивности и удобство
интеграции с компонентами.