Тестирование рекурсивных компонентов

Рекурсивные компоненты в Vue представляют собой компоненты, которые внутри своего шаблона вызывают сами себя. Такой подход часто используется для отображения вложенных структур данных, например, деревьев файлов или меню с неограниченным уровнем вложенности. Тестирование таких компонентов требует особого подхода, поскольку стандартные методы рендеринга могут привести к бесконечной рекурсии, если не ограничить глубину или не использовать специальные техники мока.


Монтирование рекурсивного компонента

Для тестирования рекурсивного компонента ключевым моментом является корректная настройка глубины монтирования. Vue Test Utils предоставляет методы mount и shallowMount.

  • mount рендерит компонент полностью, включая все дочерние компоненты.
  • shallowMount заменяет дочерние компоненты на заглушки, что предотвращает бесконечную рекурсию при рекурсивных вызовах.

Пример рекурсивного компонента:

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
      <RecursiveTree v-if="item.children" :items="item.children" />
    </li>
  </ul>
</template>

<script>
export default {
  name: 'RecursiveTree',
  props: {
    items: {
      type: Array,
      required: true
    }
  }
}
</script>

Для тестирования можно использовать shallowMount, чтобы избежать полной рекурсии:

import { shallowMount } from '@vue/test-utils'
import RecursiveTree from '@/components/RecursiveTree.vue'

const items = [
  { id: 1, name: 'Root', children: [{ id: 2, name: 'Child' }] }
]

const wrapper = shallowMount(RecursiveTree, {
  props: { items }
})

В этом случае дочерний вызов RecursiveTree будет заменен заглушкой, что позволяет проверить структуру верхнего уровня без рендеринга всех вложенных уровней.


Тестирование глубины рекурсии

Если требуется проверить корректный рендер вложенных элементов, можно использовать параметр stubs для замены рекурсивного вызова на ограниченное число уровней. Например:

const wrapper = mount(RecursiveTree, {
  props: { items },
  global: {
    stubs: {
      RecursiveTree: {
        template: '<div class="stub-recursive"></div>'
      }
    }
  }
})

Это позволяет контролировать количество рекурсивных рендеров и проверять, что дочерние элементы отображаются корректно.


Проверка структуры DOM

Vue Test Utils предоставляет методы для обхода элементов:

  • find и findAll — поиск элементов по селектору.
  • text и html — получение содержимого.
  • props и attributes — проверка переданных данных.

Пример проверки структуры дерева:

import { mount } from '@vue/test-utils'

const wrapper = mount(RecursiveTree, { props: { items } })

// Проверка наличия корневого элемента
expect(wrapper.find('li').text()).toContain('Root')

// Проверка наличия дочернего элемента через заглушку
expect(wrapper.find('.stub-recursive').exists()).toBe(true)

Использование заглушек позволяет убедиться, что рекурсивные вызовы присутствуют в дереве компонентов, не погружаясь в бесконечную рекурсию.


Симуляция пользовательских действий

Рекурсивные компоненты часто содержат интерактивные элементы, например, раскрытие/сворачивание узлов. Для их тестирования Vue Test Utils предоставляет методы trigger и emitted:

const wrapper = mount(RecursiveTree, { props: { items } })

// Симуляция клика на элемент
await wrapper.find('li').trigger('click')

// Проверка события
expect(wrapper.emitted('SELECT')).toBeTruthy()
expect(wrapper.emitted('select')[0]).toEqual([items[0]])

Такой подход позволяет убедиться, что рекурсивная структура корректно обрабатывает события на любом уровне.


Тестирование с асинхронными данными

Если рекурсивный компонент получает данные асинхронно, важно использовать nextTick или flushPromises, чтобы дождаться обновления DOM:

import { nextTick } FROM 'vue'

await nextTick()
expect(wrapper.findAll('li').length).toBe(2)

Это гарантирует корректную проверку элементов после загрузки данных и предотвращает ложные отрицательные результаты тестов.


Подходы к оптимизации тестов рекурсивных компонентов

  1. Использование shallowMount для верхнего уровня — минимизирует время выполнения и предотвращает бесконечную рекурсию.
  2. Мок дочерних вызовов — позволяет проверять логику без полного рендеринга.
  3. Контроль глубины рекурсии через props или stubs — дает возможность тестировать определенные уровни дерева.
  4. Фокус на поведении, а не на полном DOM — важно проверять реакции на события, вызовы методов и изменения данных, а не все HTML-структуры.

Особенности обработки props и слотов

Рекурсивные компоненты могут использовать слоты для кастомизации каждого узла:

<RecursiveTree :items="items">
  <template #node="{ item }">
    <span class="custom-node">{{ item.name }}</span>
  </template>
</RecursiveTree>

Тестирование слотов осуществляется через передачу slots при монтировании:

const wrapper = mount(RecursiveTree, {
  props: { items },
  slots: {
    node: '<span class="custom-node">{{ props.item.name }}</span>'
  }
})

expect(wrapper.find('.custom-node').text()).toBe('Root')

Это позволяет проверять кастомизированное отображение узлов без изменения внутренней логики компонента.


Выводы по методам тестирования

Тестирование рекурсивных компонентов требует:

  • Контроля глубины рекурсии через shallowMount или stubs.
  • Проверки структуры DOM с использованием find и findAll.
  • Симуляции событий с помощью trigger и emitted.
  • Асинхронного ожидания обновлений через nextTick.
  • Тестирования слотов и передачи props на каждом уровне.

Такой комплексный подход обеспечивает надежность и предсказуемость поведения рекурсивных компонентов в реальных приложениях.