Зависимости и provide/inject

Vue Test Utils (VTU) предоставляет инструменты для тестирования компонентов Vue, включая возможность управления зависимостями через provide и inject. Этот механизм позволяет компонентам получать данные или сервисы от родительских компонентов без необходимости явно передавать их через props, что особенно удобно при тестировании сложных иерархий компонентов.


Механизм provide и inject

  • provide используется на родительском компоненте для предоставления данных или сервисов. Обычно это объект или функция, возвращающая объект:
export default {
  provide() {
    return {
      apiService: this.apiService
    }
  },
  data() {
    return {
      apiService: new ApiService()
    }
  }
}
  • inject позволяет дочернему компоненту получать эти данные:
export default {
  inject: ['apiService'],
  mounted() {
    this.apiService.fetchData()
  }
}

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


Передача provide при монтировании компонента

При использовании VTU для монтирования компонентов можно передавать значения provide через опцию global.provide. Это особенно полезно для подмены сервисов и зависимостей:

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

const mockApiService = {
  fetchData: jest.fn().mockResolvedValue({ data: 'test' })
}

const wrapper = mount(ChildComponent, {
  global: {
    provide: {
      apiService: mockApiService
    }
  }
})

await wrapper.vm.apiService.fetchData()
expect(mockApiService.fetchData).toHaveBeenCalled()

В этом примере дочерний компонент получает мок apiService, что позволяет полностью контролировать поведение теста без обращения к реальной реализации.


Передача provide через функцию

Если provide в компоненте реализован как функция, VTU корректно вызывает её и позволяет переопределять значения:

const wrapper = mount(ChildComponent, {
  global: {
    provide: () => ({
      apiService: {
        fetchData: jest.fn()
      }
    })
  }
})

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


Моки для глобальных зависимостей

Vue Test Utils поддерживает возможность подмены глобальных зависимостей через опцию global.mocks. Разница с provide заключается в том, что mocks используется для мокирования глобальных свойств, таких как $router, $store или $t в i18n, тогда как provide/inject имитирует передачу сервисов через иерархию компонентов.

Пример подмены $store:

const wrapper = mount(ComponentUnderTest, {
  global: {
    mocks: {
      $store: {
        state: { user: { name: 'Test' } },
        dispatch: jest.fn()
      }
    }
  }
})

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


Тестирование сложной иерархии компонентов

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

import ParentComponent from '@/components/ParentComponent.vue'
import ChildComponent from '@/components/ChildComponent.vue'

const wrapper = mount(ParentComponent, {
  global: {
    provide: {
      apiService: { fetchData: jest.fn() }
    },
    stubs: {
      ChildComponent: true
    }
  }
})

С помощью stubs можно изолировать дочерние компоненты, тестируя родительский компонент независимо, или наоборот, тестировать дочерний компонент, передавая нужные зависимости через global.provide.


Важные нюансы

  1. Вложенные provide: значения provide наследуются от родителя к детям, поэтому дочерние компоненты автоматически получают данные от ближайшего родителя, если не передан локальный override.
  2. Ссылочные объекты: объекты и функции, переданные через provide, передаются по ссылке. Изменение их состояния в тесте может повлиять на другие компоненты.
  3. Комбинация с mocks: global.mocks и global.provide могут использоваться одновременно для полной подмены зависимостей, что позволяет тестировать компонент в максимально контролируемой среде.

Примеры тестов с проверкой provide/inject

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

test('должен вызвать fetchData при монтировании', async () => {
  const fetchDataMock = jest.fn()
  const wrapper = mount(ChildComponent, {
    global: {
      provide: {
        apiService: { fetchData: fetchDataMock }
      }
    }
  })

  await wrapper.vm.$nextTick()
  expect(fetchDataMock).toHaveBeenCalled()
})

Проверка реактивности provide:

import { ref } from 'vue'

test('реактивное значение provide обновляется в дочернем компоненте', async () => {
  const reactiveValue = ref('initial')
  const wrapper = mount(ChildComponent, {
    global: {
      provide: { reactiveValue }
    }
  })

  expect(wrapper.text()).toContain('initial')
  reactiveValue.value = 'updated'
  await wrapper.vm.$nextTick()
  expect(wrapper.text()).toContain('updated')
})

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