Тестирование Vuex stores

Тестирование Vuex store — критически важный этап в разработке приложений на Vue.js, поскольку store управляет состоянием и логикой приложения. Vue Test Utils предоставляет инструменты для интеграции с компонентами, однако для unit-тестирования store лучше использовать специализированные подходы, позволяющие изолировать логику.

Структура Vuex store

Vuex store состоит из state, getters, mutations, actions и modules. При тестировании важно разделять эти части, так как каждая требует специфического подхода:

  • State — хранит данные приложения. Тестирование state обычно ограничивается проверкой начального состояния и состояния после вызова мутаций.
  • Getters — вычисляемые свойства, зависящие от state. Проверяются как чистые функции: передаются входные данные, проверяется результат.
  • Mutations — синхронные методы, изменяющие state. Тестируются, вызывая их напрямую и проверяя изменения state.
  • Actions — могут быть асинхронными, выполняют побочные эффекты и вызывают мутации. Тестирование actions включает мокинг commit и dispatch.
  • Modules — позволяют структурировать store. Тестирование модулей идентично тестированию отдельного store.

Тестирование state и mutations

State и mutations являются самым простым для unit-тестирования уровнем. Для этого создается изолированный store:

import { createStore } from 'vuex'
import counter from '@/store/modules/counter'

describe('mutations', () => {
  it('increment увеличивает значение state на 1', () => {
    const state = { count: 0 }
    counter.mutations.increment(state)
    expect(state.count).toBe(1)
  })

  it('decrement уменьшает значение state на 1', () => {
    const state = { count: 2 }
    counter.mutations.decrement(state)
    expect(state.count).toBe(1)
  })
})

Ключевой момент: mutations всегда вызываются с state, и их результат проверяется через непосредственное изменение этого state.

Тестирование getters

Getters — чистые функции, принимающие state и возвращающие вычисленное значение:

describe('getters', () => {
  it('doubleCount возвращает удвоенное значение count', () => {
    const state = { count: 5 }
    const result = counter.getters.doubleCount(state)
    expect(result).toBe(10)
  })
})

Важно проверять не только корректность вычислений, но и поведение при граничных значениях state.

Тестирование actions

Actions сложнее из-за асинхронности и зависимости от commit/dispatch. Обычно используют мок-функции для этих методов:

describe('actions', () => {
  it('incrementAsync вызывает commit после задержки', async () => {
    const commit = jest.fn()
    await counter.actions.incrementAsync({ commit })
    expect(commit).toHaveBeenCalledWith('increment')
  })
})

Если action использует API-запросы, необходимо мокировать внешние вызовы с помощью jest.mock или axios-mock-adapter, чтобы тест оставался изолированным.

Тестирование модулей

Модули Vuex можно тестировать отдельно, создавая store с конкретным модулем:

import { createStore } from 'vuex'
import counter from '@/store/modules/counter'

const store = createStore({
  modules: {
    counter
  }
})

describe('module counter', () => {
  it('инициализируется с правильным state', () => {
    expect(store.state.counter.count).toBe(0)
  })

  it('commit mutation через store работает', () => {
    store.commit('counter/increment')
    expect(store.state.counter.count).toBe(1)
  })
})

Обязательное правило: namespace модулей влияет на вызов commit/dispatch, что нужно учитывать при тестировании.

Интеграционное тестирование с компонентами

Vue Test Utils позволяет подключать store к компонентам и проверять взаимодействие компонентов с state:

import { mount } from '@vue/test-utils'
import Counter from '@/components/Counter.vue'
import { createStore } from 'vuex'
import counter from '@/store/modules/counter'

const store = createStore({
  modules: { counter }
})

describe('Counter.vue', () => {
  it('отображает текущее значение count', () => {
    const wrapper = mount(Counter, { global: { plugins: [store] } })
    expect(wrapper.text()).toContain('0')
  })

  it('увеличивает count при клике на кнопку', async () => {
    const wrapper = mount(Counter, { global: { plugins: [store] } })
    await wrapper.find('button.increment').trigger('click')
    expect(store.state.counter.count).toBe(1)
  })
})

Тестирование через компонент позволяет проверять интеграцию store и UI, однако для unit-тестов предпочтительно тестировать store отдельно.

Рекомендации по организации тестов

  • Разделять тесты для mutations, actions, getters.
  • Для actions использовать мок commit/dispatch.
  • Для modules учитывать namespace.
  • Для интеграции компонентов использовать Vue Test Utils с глобальной регистрацией store.
  • Проверять граничные состояния и ошибки, чтобы обеспечить устойчивость логики.

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