Изоляция компонента от store

При тестировании компонентов Vue часто возникает необходимость изолировать компонент от глобального состояния, управляемого Vuex или Pinia. Это позволяет проверять поведение компонента независимо от внешних зависимостей, обеспечивая стабильность и предсказуемость тестов.


Монтирование компонента с mock store

Основной способ изоляции — замена реального store на мок-объект, который имитирует поведение настоящего состояния и методов. В Vue Test Utils это делается через опцию global.plugins при монтировании компонента:

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

const mockStore = {
  state: { count: 5 },
  getters: { doubleCount: () => 10 },
  dispatch: jest.fn(),
  commit: jest.fn()
}

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $store: mockStore
    }
  }
})

В этом примере:

  • state и getters позволяют компоненту получать данные, как если бы он работал с реальным store.
  • dispatch и commit заменены на jest-функции, что позволяет отслеживать вызовы действий и мутаций без изменения настоящего состояния.

Изоляция через provide/inject

Если компонент использует inject для доступа к store или отдельным сервисам, можно подменять зависимости при монтировании через global.provide:

const mockService = {
  fetchData: jest.fn().mockResolvedValue({ items: [1,2,3] })
}

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

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


Замена модулей store с createStore

В Vue 3 с Vuex 4 или Pinia можно создавать отдельный store специально для теста:

import { createStore } from 'vuex'

const testStore = createStore({
  state() {
    return { count: 0 }
  },
  mutations: {
    increment(state) { state.count++ }
  }
})

const wrapper = mount(MyComponent, {
  global: {
    plugins: [testStore]
  }
})

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


Изоляция действий и мутаций

Для тестирования поведения компонента при вызове store-методов используют подмену функций на мок-версии. Например:

const commitMock = jest.fn()
const dispatchMock = jest.fn()

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $store: {
        state: { count: 0 },
        commit: commitMock,
        dispatch: dispatchMock
      }
    }
  }
})

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

expect(commitMock).toHaveBeenCalledWith('increment')
expect(dispatchMock).not.toHaveBeenCalled()

Такой подход позволяет:

  • Проверять вызовы конкретных мутаций и действий.
  • Не зависеть от реализации store.
  • Сохранять тесты быстрыми и предсказуемыми.

Совместная работа с computed и watch

При изоляции store важно помнить, что computed свойства и watch в компоненте продолжают реагировать на изменения мок-состояния:

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $store: { state: { count: 1 } }
    }
  }
})

expect(wrapper.vm.doubleCount).toBe(2) // если компонент вычисляет doubleCount через store

Чтобы протестировать реакцию на изменение состояния:

wrapper.vm.$store.state.count = 5
await wrapper.vm.$nextTick()

expect(wrapper.vm.doubleCount).toBe(10)

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


Поддержка нескольких store в одном тесте

Иногда компонент использует несколько модулей store. Для изоляции их можно подменять отдельными модулями:

const moduleA = { state: { valueA: 1 }, getters: { doubleA: () => 2 } }
const moduleB = { state: { valueB: 3 }, getters: { doubleB: () => 6 } }

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $store: {
        state: { moduleA, moduleB },
        getters: { doubleA: moduleA.getters.doubleA, doubleB: moduleB.getters.doubleB }
      }
    }
  }
})

Это позволяет изолированно тестировать каждый модуль, не создавая полноценный store с полной логикой приложения.


Практические советы

  • Использовать jest.fn() для всех методов, чтобы можно было отслеживать вызовы.
  • Создавать минимальный mock store: только то, что реально нужно компоненту.
  • Не импортировать реальный store в unit-тестах, это нарушает принцип изоляции.
  • Обновлять состояние вручную через mock, если нужно проверить реактивность компонента.
  • Комбинировать provide/inject и mocks для комплексных зависимостей.

Изоляция компонента от store обеспечивает чистые, предсказуемые и быстрые unit-тесты, позволяя фокусироваться на логике самого компонента, а не на состоянии приложения.