Интеграция с TypeScript

Интеграция Vue Test Utils с TypeScript начинается с корректной конфигурации проекта. Ключевая цель — обеспечить строгую типизацию компонентов, тестовых утилит и вспомогательных функций без ухудшения DX и увеличения количества приведений типов.

Базовый набор зависимостей включает:

  • typescript
  • vue
  • @vue/test-utils
  • vitest или jest
  • типы для тест-раннера (@types/jest, если используется Jest)

Файл tsconfig.json должен учитывать специфику тестов:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "types": ["vitest/globals", "node"],
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src", "tests"]
}

Важно, чтобы каталог с тестами входил в область компиляции. Это позволяет TypeScript анализировать типы компонентов, моков и хелперов.


Типизация компонентов при тестировании

Vue Test Utils предоставляет обобщённый тип VueWrapper<T>, где T — тип экземпляра компонента. При использовании mount или shallowMount TypeScript способен вывести тип автоматически, если компонент объявлен корректно.

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

const wrapper = mount(MyComponent)

В этом случае wrapper.vm имеет тип ComponentPublicInstance, но без уточнения пропсов и методов. Для более строгой типизации используется явное указание типа:

const wrapper = mount<typeof MyComponent>(MyComponent)

Теперь wrapper.vm содержит публичные свойства компонента, включая props, computed, methods, emits.


Типизация props и emits

TypeScript особенно полезен при тестировании контрактов компонента.

Компонент с типизированными пропсами:

export default defineComponent({
  props: {
    count: {
      type: Number,
      required: true
    }
  },
  emits: {
    increment: (value: number) => value > 0
  }
})

При монтировании:

mount(MyComponent, {
  props: {
    count: 5
  }
})

Передача некорректного типа пропса приводит к ошибке компиляции. Аналогично, при проверке событий:

wrapper.vm.$emit('increment', 'wrong')

TypeScript сигнализирует о несоответствии сигнатуре emits.


Работа с wrapper.vm и публичным API

По умолчанию Vue Test Utils предоставляет доступ только к публичному API компонента. Это совпадает с философией Vue 3 и хорошо сочетается с TypeScript.

Для компонентов, использующих setup, важно возвращать свойства, необходимые для тестов:

setup() {
  const value = ref(0)
  const inc = () => value.value++

  return { value, inc }
}

В тестах:

wrapper.vm.inc()
expect(wrapper.vm.value).toBe(1)

TypeScript точно знает типы value и inc, без any и без приведения типов.


Типизация find и работы с DOM

Методы find и findAll возвращают обобщённые типы:

  • DOMWrapper<Element>
  • VueWrapper<ComponentPublicInstance>

Пример:

const button = wrapper.find('button')
button.trigger('click')

При необходимости доступа к конкретному элементу:

const input = wrapper.find('input').element as HTMLInputElement
expect(input.value).toBe('text')

Приведение типов допустимо только на уровне DOM, а не компонентов. Это сохраняет строгую типизацию бизнес-логики.


Типизация слотов

Vue Test Utils поддерживает типизированные слоты через slots:

mount(MyComponent, {
  slots: {
    default: '<span>Content</span>'
  }
})

Для scoped slots:

slots: {
  default: (props: { value: number }) => `Value: ${props.value}`
}

TypeScript проверяет сигнатуру слота, если компонент корректно типизирован.


Глобальные компоненты и плагины

При использовании global конфигурации важно сохранять типовую согласованность:

mount(MyComponent, {
  global: {
    components: { BaseButton },
    plugins: [router]
  }
})

Для плагинов с расширением app.config.globalProperties рекомендуется объявлять глобальные типы:

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $api: ApiClient
  }
}

После этого $api доступен в wrapper.vm с корректным типом.


Моки и стабсы с типами

Vue Test Utils позволяет использовать vi.fn() или jest.fn() с полной типизацией:

const onCl ick = vi.fn<(value: number) => void>()

При передаче в props:

mount(MyComponent, {
  props: {
    onClick
  }
})

TypeScript гарантирует совпадение сигнатуры функции с ожиданиями компонента.

Для стабов компонентов:

stubs: {
  ChildComponent: {
    template: '<div />'
  }
}

Если требуется типизация интерфейса дочернего компонента, предпочтительнее использовать реальный компонент или функциональный стаб с явным типом пропсов.


Асинхронность и типы nextTick

Асинхронные обновления Vue корректно типизируются:

await wrapper.vm.$nextTick()

nextTick возвращает Promise<void>, что упрощает цепочки ожиданий и предотвращает ошибки синхронизации.


Пользовательские хелперы и фабрики

Для повторного использования логики создаются типизированные фабрики:

function factory(props?: Partial<Props>) {
  return mount(MyComponent, {
    props: {
      count: 1,
      ...props
    }
  })
}

Partial<Props> позволяет передавать только необходимые параметры, сохраняя контроль типов.


Типобезопасное тестирование как часть архитектуры

Интеграция Vue Test Utils с TypeScript делает тесты частью контрактной системы приложения. Ошибки в пропсах, событиях и публичных методах выявляются на этапе компиляции. Это снижает количество неявных допущений, упрощает рефакторинг и делает тесты устойчивыми к изменению внутренней реализации компонентов.