Тестирование динамических компонентов

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


Монтирование динамических компонентов

Для тестирования динамических компонентов используется метод mount или shallowMount из Vue Test Utils. Динамическим компонентом может быть обычный компонент Vue, переданный через :is:

import { mount } from '@vue/test-utils'
import DynamicComponent from '@/components/DynamicComponent.vue'
import ComponentA from '@/components/ComponentA.vue'
import ComponentB from '@/components/ComponentB.vue'

describe('DynamicComponent', () => {
  it('рендерит правильный компонент в зависимости от пропса', () => {
    const wrapperA = mount(DynamicComponent, {
      props: { componentType: 'A' },
      global: { components: { ComponentA, ComponentB } }
    })
    expect(wrapperA.findComponent(ComponentA).exists()).toBe(true)

    const wrapperB = mount(DynamicComponent, {
      props: { componentType: 'B' },
      global: { components: { ComponentA, ComponentB } }
    })
    expect(wrapperB.findComponent(ComponentB).exists()).toBe(true)
  })
})

Ключевой момент: для корректного рендеринга Vue должен знать о компонентах, которые могут использоваться динамически. Это достигается через global.components при монтировании в тесте.


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

Vue поддерживает асинхронные компоненты, которые загружаются по требованию. Для их тестирования необходимо использовать асинхронное ожидание рендера через nextTick или flushPromises:

import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import AsyncDynamicComponent from '@/components/AsyncDynamicComponent.vue'

jest.mock('@/components/ComponentC.vue', () => ({
  template: '<div>Компонент C</div>'
}))

it('асинхронно рендерит компонент', async () => {
  const wrapper = mount(AsyncDynamicComponent, {
    props: { componentType: 'C' }
  })
  
  await nextTick()
  
  expect(wrapper.text()).toContain('Компонент C')
})

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


Проверка пропсов и событий динамических компонентов

Динамические компоненты могут получать различные пропсы и эмитить события. Vue Test Utils позволяет тестировать взаимодействия и реакцию на эти изменения:

import { mount } from '@vue/test-utils'
import DynamicComponent from '@/components/DynamicComponent.vue'
import ComponentD from '@/components/ComponentD.vue'

it('передаёт пропсы и обрабатывает события', async () => {
  const wrapper = mount(DynamicComponent, {
    props: { componentType: 'D', message: 'Тест' },
    global: { components: { ComponentD } }
  })

  const componentD = wrapper.findComponent(ComponentD)
  expect(componentD.props('message')).toBe('Тест')

  await componentD.vm.$emit('update', 42)
  expect(wrapper.emitted('update')[0]).toEqual([42])
})

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


Тестирование слотов динамических компонентов

Динамические компоненты могут иметь слоты, и их тестирование включает проверку рендера вложенного контента:

import { mount } from '@vue/test-utils'
import DynamicWithSlot from '@/components/DynamicWithSlot.vue'
import ComponentE from '@/components/ComponentE.vue'

it('рендерит слот внутри динамического компонента', () => {
  const wrapper = mount(DynamicWithSlot, {
    props: { componentType: 'E' },
    global: { components: { ComponentE } },
    slots: { default: '<span class="slot-content">Слот</span>' }
  })

  expect(wrapper.find('.slot-content').exists()).toBe(true)
  expect(wrapper.find('.slot-content').text()).toBe('Слот')
})

Слоты могут быть как простыми HTML-фрагментами, так и другими компонентами, что делает проверку гибкой и важной для динамических интерфейсов.


Изоляция тестов и мок компонентов

Для упрощения тестирования сложных динамических компонентов часто используют заглушки (stubs) или мок-версии компонентов:

const wrapper = mount(DynamicComponent, {
  props: { componentType: 'F' },
  global: { stubs: ['ComponentF'] }
})
expect(wrapper.findComponent({ name: 'ComponentF' }).exists()).toBe(true)

Стратегия использования stub-компонентов снижает зависимость теста от реализации дочерних компонентов, ускоряет выполнение тестов и делает их более предсказуемыми.


Практика тестирования смены динамических компонентов

Динамическое обновление компонента часто зависит от реактивного изменения пропсов или состояния:

it('смена компонента при обновлении пропса', async () => {
  const wrapper = mount(DynamicComponent, {
    props: { componentType: 'A' },
    global: { components: { ComponentA, ComponentB } }
  })

  expect(wrapper.findComponent(ComponentA).exists()).toBe(true)
  await wrapper.setProps({ componentType: 'B' })
  expect(wrapper.findComponent(ComponentB).exists()).toBe(true)
})

Использование setProps позволяет проверить реакцию Vue на изменения пропсов и корректность обновления динамического рендера.


Советы по отладке тестов динамических компонентов

  • Проверять наличие компонентов через findComponent, а не через text(), чтобы избежать ложноположительных результатов.
  • Использовать global.components для регистрации всех потенциальных компонентов, которые могут рендериться.
  • Для асинхронных компонентов сочетать nextTick и flushPromises для надежного ожидания рендера.
  • Изолировать сложные дочерние компоненты через stubs или мок-файлы, чтобы тестировать только логику родителя.

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