При тестировании компонентов Vue, которые используют несколько composables, важно понимать, как правильно изолировать логику и проверять её корректность без необходимости рендерить весь компонент. Composables позволяют организовать повторно используемую реактивную логику, но при их совмещении возникает несколько тонкостей, связанных с mock-ами, реактивностью и взаимодействием между ними.
mount и shallowMount из Vue Test Utils
являются основными инструментами для создания экземпляров компонентов.
При тестировании компонентов с несколькими composables:
Изоляция composable Если один composable зависит от внешних сервисов или глобальных состояний, его нужно замокать. Это помогает проверять компонент без сторонних эффектов.
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
import * as useAuthComposable from '@/composables/useAuth';
import * as useApiComposable from '@/composables/useApi';
jest.mock('@/composables/useAuth');
jest.mock('@/composables/useApi');
useAuthComposable.useAuth.mockReturnValue({
isLoggedIn: true,
user: { name: 'Alice' }
});
useApiComposable.useApi.mockReturnValue({
fetchData: jest.fn().mockResolvedValue([1, 2, 3])
});
const wrapper = mount(MyComponent);shallowMount для изоляции дочерних компонентов
shallowMount предотвращает рендеринг дочерних компонентов,
что особенно полезно при сложной логике, где composables передают данные
в несколько уровней компонентов.
import { shallowMount } from '@vue/test-utils';
const wrapper = shallowMount(MyComponent);Когда в компоненте используется несколько composables, их состояния могут зависеть друг от друга. Для корректного тестирования важно:
nextTick для ожидания обновления
реактивных данных.import { nextTick } from 'vue';
test('компонент корректно объединяет состояния composables', async () => {
const wrapper = mount(MyComponent);
expect(wrapper.vm.isLoggedIn).toBe(true);
expect(wrapper.vm.data.length).toBe(3);
wrapper.vm.data.push(4);
await nextTick();
expect(wrapper.vm.data).toEqual([1, 2, 3, 4]);
});
Если composable содержит методы, которые вызывают API или изменяют глобальное состояние, их можно замокать или использовать spy, чтобы проверить вызовы и аргументы:
const fetchDataSpy = jest.fn();
useApiComposable.useApi.mockReturnValue({
fetchData: fetchDataSpy
});
wrapper.vm.loadData();
expect(fetchDataSpy).toHaveBeenCalledWith({ page: 1 });
Компоненты с несколькими composables часто имеют реактивные
зависимости, включая watch и watchEffect. Для
их проверки используется комбинация nextTick и прямой
манипуляции состояниями composables:
test('watch корректно реагирует на изменения состояния', async () => {
const wrapper = mount(MyComponent);
wrapper.vm.filter = 'new';
await nextTick();
expect(wrapper.vm.filteredData).toEqual([/* ожидаемый результат */]);
});
Если composables используют provide/inject для обмена
данными между компонентами, можно мокать inject или
создавать тестовый провайдер:
import { provide } from 'vue';
test('компонент получает данные через provide/inject', () => {
const wrapper = mount(MyComponent, {
global: {
provide: {
apiService: {
fetch: jest.fn().mockResolvedValue([1, 2])
}
}
}
});
expect(wrapper.vm.data).toEqual([]);
});
Тестирование composable отдельно Каждый composable можно протестировать как отдельную функцию, проверяя его реактивное состояние, методы и side-effect. Это уменьшает сложность тестов компонентов.
Интеграционное тестирование с компонентом После проверки composables отдельно важно протестировать их совместную работу внутри компонента. Здесь проверяются:
Использование spy и mock для минимизации внешних зависимостей Любые обращения к API, localStorage или глобальным сервисам стоит мокать, чтобы тест оставался детерминированным.
Компонент Dashboard.vue использует два composables:
useAuth для авторизации и useStats для
получения статистики.
// useAuth.js
export function useAuth() {
const isLoggedIn = ref(false);
const user = ref(null);
return { isLoggedIn, user };
}
// useStats.js
export function useStats() {
const stats = ref([]);
const fetchStats = async () => {
stats.value = await fetch('/api/stats').then(res => res.json());
};
return { stats, fetchStats };
}
Тест для Dashboard.vue с проверкой интеграции
composables:
import { mount } from '@vue/test-utils';
import Dashboard from '@/components/Dashboard.vue';
import * as useAuthComposable from '@/composables/useAuth';
import * as useStatsComposable from '@/composables/useStats';
jest.mock('@/composables/useAuth');
jest.mock('@/composables/useStats');
useAuthComposable.useAuth.mockReturnValue({ isLoggedIn: true, user: { name: 'Alice' } });
useStatsComposable.useStats.mockReturnValue({
stats: ref([10, 20, 30]),
fetchStats: jest.fn()
});
test('Dashboard корректно объединяет composables', () => {
const wrapper = mount(Dashboard);
expect(wrapper.vm.isLoggedIn).toBe(true);
expect(wrapper.vm.user.name).toBe('Alice');
expect(wrapper.vm.stats).toEqual([10, 20, 30]);
});
Использование такой стратегии позволяет писать поддерживаемые и надёжные тесты компонентов, которые используют сложные комбинации нескольких composables, сохраняя при этом высокую степень изоляции и предсказуемости.