Provide и inject в тестах

Механизмы provide и inject в Vue используются для передачи данных между компонентами без необходимости передавать их через props на каждом уровне. В контексте тестирования это особенно важно, так как позволяет имитировать контексты зависимостей и проверять поведение компонентов, зависящих от внешних сервисов, настроек или состояния.

Основы provide и inject

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

// Родительский компонент
export default {
  provide() {
    return {
      apiService: this.apiService
    };
  },
  data() {
    return {
      apiService: { fetchData: () => 'данные' }
    };
  }
}

// Дочерний компонент
export default {
  inject: ['apiService'],
  mounted() {
    console.log(this.apiService.fetchData()); // 'данные'
  }
}

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


Подстановка provide в Vue Test Utils

Vue Test Utils позволяет передавать provide через опцию global.provide при монтировании компонента с помощью mount или shallowMount.

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

const mockApiService = {
  fetchData: jest.fn(() => 'тестовые данные')
};

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

expect(wrapper.vm.apiService.fetchData()).toBe('тестовые данные');
expect(mockApiService.fetchData).toHaveBeenCalled();

Ключевые моменты:

  • Глобальный provide передается через объект global.provide.
  • Можно подменять реальные сервисы моками или шпионами (spies) для отслеживания вызовов.
  • Поддерживаются функции и значения, передаваемые через provide.

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

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

const mockFunction = jest.fn();

const wrapper = mount(ChildComponent, {
  global: {
    provide: {
      doSomething: mockFunction
    }
  }
});

wrapper.find('button').trigger('click');

expect(mockFunction).toHaveBeenCalledTimes(1);

Особенность: даже если provide содержит функцию, Vue Test Utils корректно сохраняет контекст и позволяет работать с реактивными или обычными функциями.


Тестирование реактивного provide

Vue 3 позволяет передавать реактивные объекты через provide, используя reactive или ref. В тестах это дает возможность проверять реактивность компонентов.

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

const state = reactive({ count: 0 });

const wrapper = mount(ChildComponent, {
  global: {
    provide: {
      counterState: state
    }
  }
});

expect(wrapper.text()).toContain('0');

state.count++;
await wrapper.vm.$nextTick();

expect(wrapper.text()).toContain('1');

Выводы по реактивному provide:

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

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

Если компонент находится глубоко в дереве и зависит от provide верхнего уровня, его можно протестировать без необходимости монтировать всех родителей:

const wrapper = mount(DeepChildComponent, {
  global: {
    provide: {
      userService: {
        getUser: () => ({ name: 'Тестовый пользователь' })
      }
    }
  }
});

expect(wrapper.text()).toContain('Тестовый пользователь');

Преимущество:

  • Изоляция компонента от внешнего окружения.
  • Возможность точечной подмены зависимостей.

Моки и шпионы для inject

Часто важно отслеживать вызовы методов, предоставляемых через provide. Jest позволяет создавать моки и шпионы для этих целей:

const mockService = {
  fetchData: jest.fn(() => 'ответ')
};

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

wrapper.vm.apiService.fetchData();

expect(mockService.fetchData).toHaveBeenCalledTimes(1);
  • Позволяет проверять, что компонент корректно использует зависимости.
  • Можно легко тестировать разные сценарии, возвращая разные данные.

Особенности shallowMount

При использовании shallowMount компоненты-потомки рендерятся как заглушки, но provide/inject работает корректно. Это дает возможность тестировать логику дочернего компонента без рендеринга всей иерархии.

import { shallowMount } from '@vue/test-utils';

const wrapper = shallowMount(ChildComponent, {
  global: {
    provide: {
      value: 42
    }
  }
});

expect(wrapper.vm.value).toBe(42);

Советы по организации тестов

  1. Мокать только нужные зависимости: подменять полный сервис необязательно, достаточно методов, используемых в тесте.
  2. Использовать реактивные объекты для состояния: позволяет проверять динамическое поведение компонента.
  3. Изолировать тестируемый компонент: не монтировать лишних родителей, достаточно глобального provide.
  4. Комбинировать с jest.fn(): для отслеживания вызовов функций через inject.

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