Тестирование Vue-компонентов часто требует изоляции их внутренней логики от внешних зависимостей. Composables (функции из Composition API) предоставляют мощный способ организации логики, но при этом могут создавать сложности для юнит-тестов, если они взаимодействуют с состоянием приложения, API или сторонними сервисами. Использование моков для composables позволяет протестировать компонент в контролируемой среде, не затрагивая реальные реализации зависимостей.
Изоляция логики Основная цель мокирования — отделить компонент от реальной логики composable, сохранив при этом интерфейс. Компонент должен продолжать вызывать функции и реактивные данные, но их значения контролируются тестом.
Стабильность тестов Моки позволяют создавать повторяемые сценарии. Независимо от состояния API или стороннего сервиса, результат выполнения функции в тесте будет предсказуемым.
Контроль реактивности Composables часто
возвращают ref, reactive или вычисляемые
свойства (computed). Мок должен поддерживать реактивность,
чтобы изменения значений внутри теста корректно обновляли
компонент.
jest.mock()Для composables, экспортируемых как модули, можно использовать встроенные возможности Jest:
// useUser.js
import { ref } from 'vue';
export function useUser() {
const user = ref(null);
function fetchUser() {
user.value = { id: 1, name: 'Alice' };
}
return { user, fetchUser };
}
В тесте:
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
import * as userComposable from '@/composables/useUser';
jest.mock('@/composables/useUser', () => ({
useUser: jest.fn(),
}));
test('отображает имя пользователя', async () => {
const mockUser = { id: 1, name: 'Mocked User' };
userComposable.useUser.mockReturnValue({
user: ref(mockUser),
fetchUser: jest.fn(),
});
const wrapper = mount(MyComponent);
expect(wrapper.text()).toContain('Mocked User');
});
Ключевые моменты:
jest.mock() для перехвата импорта
composable.user,
fetchUser), что и оригинальный composable.ref)
для проверки поведения компонента.Иногда composable можно проектировать так, чтобы его зависимости можно было передавать извне:
export function useApi(fetchFn) {
const data = ref(null);
async function load() {
data.value = await fetchFn();
}
return { data, load };
}
В тесте:
const mockFetch = jest.fn().mockResolvedValue({ id: 1 });
const { data, load } = useApi(mockFetch);
await load();
expect(data.value.id).toBe(1);
Такой подход уменьшает необходимость глобального мокирования и делает тесты более явными.
provide/inject для composablesЕсли composable использует inject для получения
зависимостей, их можно легко заменять через provide в
тесте:
// useSettings.js
import { inject } from 'vue';
export function useSettings() {
const settings = inject('settings', { theme: 'light' });
return { settings };
}
Тест:
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
import { h, provide } from 'vue';
const wrapper = mount({
setup() {
provide('settings', { theme: 'dark' });
return () => h(MyComponent);
}
});
expect(wrapper.html()).toContain('dark');
ref и reactive внутри мока
обязательно, если компонент ожидает реактивные объекты.Асинхронные composables с загрузкой данных
ref с начальными значениями.ref через nextTick.Composables с подписками или watch
flushPromises для обработки
асинхронных изменений.Composables с зависимостями от Vue Router или Pinia
setActivePinia(createPinia()).useRoute и
useRouter на мок-объекты.Использование моков для composables обеспечивает высокую изоляцию тестов и позволяет полностью контролировать сценарии без зависимости от внешних факторов, сохраняя реактивность и интерфейс компонента. Такой подход делает юнит-тестирование Vue 3 компонентов предсказуемым, безопасным и масштабируемым.