В Vue 3 жизненный цикл компонентов и реактивные хуки играют ключевую роль в управлении состоянием, побочными эффектами и синхронизацией данных. При тестировании компонентов важно не только проверять результат работы, но и корректность последовательности вызовов хуков. Неправильный порядок может привести к неожиданным багам, особенно при асинхронных операциях или при взаимодействии с родительскими и дочерними компонентами.
Vue предоставляет несколько жизненных хуков, на которые ориентируются при тестировании:
beforeCreate — вызывается сразу после инициализации
экземпляра, до создания реактивных свойств.created — происходит после создания реактивных свойств,
но до монтирования DOM.beforeMount — вызывается перед монтированием компонента
в DOM.mounted — вызывается после того, как компонент
смонтирован в DOM.beforeUpdate — вызывается перед обновлением реактивных
данных и перерисовкой.updated — вызывается после обновления данных и
перерисовки компонента.beforeUnmount — вызывается перед уничтожением
компонента.unmounted — вызывается после уничтожения
компонента.Понимание этих хуков позволяет проверять не только их наличие, но и правильную последовательность, что особенно важно при сложных компонентах с вложенной логикой.
Библиотека Vue Test Utils предоставляет несколько способов отслеживания вызовов хуков:
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
jest.spyOn(MyComponent.methods, 'myMethod');
const wrapper = mount(MyComponent);
expect(MyComponent.methods.myMethod).toHaveBeenCalled();
Для жизненных хуков Vue 3 применяются спай функции:
const beforeMountSpy = jest.fn();
const mountedSpy = jest.fn();
const wrapper = mount({
template: '<div></div>',
beforeMount() { beforeMountSpy(); },
mounted() { mountedSpy(); },
});
expect(beforeMountSpy).toHaveBeenCalledBefore(mountedSpy);
В данном примере важно не только зафиксировать факт вызова, но и
последовательность через toHaveBeenCalledBefore.
Асинхронные действия могут смещать порядок вызовов, особенно если
используется nextTick или setTimeout. Для
корректного тестирования используют асинхронные ожидания:
import { nextTick } from 'vue';
const updatedSpy = jest.fn();
const wrapper = mount({
data() {
return { count: 0 };
},
updated() {
updatedSpy();
},
template: '<div>{{ count }}</div>',
});
wrapper.vm.count++;
await nextTick();
expect(updatedSpy).toHaveBeenCalled();
await nextTick() гарантирует, что все реактивные
изменения завершены, и хук updated сработал корректно.
При сложных компонентах возникает необходимость отслеживать несколько хуков для анализа их взаимодействия:
const callOrder = [];
const wrapper = mount({
template: '<div></div>',
beforeCreate() { callOrder.push('beforeCreate'); },
created() { callOrder.push('created'); },
beforeMount() { callOrder.push('beforeMount'); },
mounted() { callOrder.push('mounted'); },
});
expect(callOrder).toEqual([
'beforeCreate',
'created',
'beforeMount',
'mounted'
]);
Этот метод позволяет тестировать полный порядок вызовов, включая потенциальные побочные эффекты, связанные с инициализацией данных и DOM.
onMounted. В этом случае спай применяется к самим хукам
внутри setup.import { onMounted } from 'vue';
const onMounted Spy = jest.fn();
mount({
setup() {
onMounted(onMountedSpy);
},
template: '<div></div>'
});
expect(onMountedSpy).toHaveBeenCalled();
jest.fn() или vi.fn() для
отслеживания вызовов хуков.await nextTick() при тестировании хуков,
связанных с обновлением DOM или реактивных данных.toHaveBeenCalledBefore).setup использовать композиционные функции
(onMounted, onUpdated) и спай функции внутри
setup.Композиционные хуки (custom hooks) часто объединяют несколько жизненных циклов и реактивных состояний. Для проверки порядка их вызова создают обёртки, где каждый внутренний вызов логируется:
function useCustomHook() {
const calls = [];
onMounted(() => calls.push('mounted'));
onUpdated(() => calls.push('updated'));
return calls;
}
const wrapper = mount({
setup() {
const calls = useCustomHook();
return { calls };
},
template: '<div></div>'
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.calls).toContain('mounted');
Такой подход позволяет контролировать последовательность вызовов внутри сложных хук-комбинаций.
При работе с библиотеками, использующими собственные хуки (например, Vuetify, Element Plus), важно проверять, что пользовательские хуки вызываются после необходимых библиотечных инициализаций. Для этого применяются логирующие спаи и массивы вызовов с последующей проверкой порядка.
Использование этих методов позволяет создавать надежные тесты, гарантировать правильный порядок вызова хуков и предотвращать ошибки, связанные с асинхронными обновлениями или сложной иерархией компонентов.