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

Pinia является официальной библиотекой управления состоянием для Vue 3 и полностью заменяет Vuex. Тестирование Pinia stores играет ключевую роль в обеспечении стабильности и предсказуемости поведения приложения. Рассмотрим детально подходы и практики для юнит-тестирования stores.


Настройка окружения для тестов

Для тестирования Pinia stores обычно используется комбинация Vue Test Utils и тестового раннера, например Vitest или Jest. Основные зависимости:

npm install @vue/test-utils pinia vitest

В тестах необходимо создавать изолированные экземпляры Pinia для предотвращения конфликтов между тестами. Это достигается через функцию createPinia().

import { createPinia, setActivePinia } from 'pinia'

beforeEach(() => {
  setActivePinia(createPinia())
})

setActivePinia задаёт активный store, к которому будут обращаться вызовы useStore() внутри тестов.


Создание и импорт store

Пример простого store:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    incrementBy(value) {
      this.count += value
    }
  }
})

Для тестирования необходимо импортировать этот store и создавать его экземпляры в тестах.

import { useCounterStore } from '@/stores/counter'

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

Юнит-тест состояния проверяет начальные значения и изменения через actions.

import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'

describe('Counter Store', () => {
  let store

  beforeEach(() => {
    setActivePinia(createPinia())
    store = useCounterStore()
  })

  it('должен иметь начальное состояние count = 0', () => {
    expect(store.count).toBe(0)
  })

  it('increment увеличивает count на 1', () => {
    store.increment()
    expect(store.count).toBe(1)
  })

  it('incrementBy корректно увеличивает count на указанное значение', () => {
    store.incrementBy(5)
    expect(store.count).toBe(5)
  })
})

Ключевой момент: beforeEach гарантирует, что каждый тест получает новый изолированный экземпляр store.


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

Геттеры в Pinia — это вычисляемые свойства. Их тестирование похоже на проверку методов.

it('doubleCount возвращает удвоенное значение count', () => {
  store.count = 3
  expect(store.doubleCount).toBe(6)
})

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


Тестирование actions с асинхронностью

Actions могут быть асинхронными, например при работе с API. Для таких случаев важно использовать async/await и мокать сетевые запросы.

import axios from 'axios'
vi.mock('axios')

it('fetchData обновляет state', async () => {
  const data = { value: 10 }
  axios.get.mockResolvedValue({ data })

  store.count = 0
  await store.fetchData()
  expect(store.count).toBe(10)
})

Замечание: использование vi.mock или jest.mock позволяет изолировать store от внешних зависимостей.


Тестирование реактивности и подписок

Pinia предоставляет возможность подписки на изменения state с помощью $subscribe. В тестах это позволяет проверять реакции на изменения.

it('срабатывает подписка при изменении state', () => {
  const callback = vi.fn()
  store.$subscribe(callback)

  store.increment()
  expect(callback).toHaveBeenCalled()
})

Подписки полезны для тестирования побочных эффектов, например логирования или синхронизации состояния с локальным хранилищем.


Тестирование с mock-пинами и зависимостями

Иногда store зависит от других store или внешних сервисов. Для корректного тестирования используют мокирование зависимостей:

import { useOtherStore } from '@/stores/other'
vi.mock('@/stores/other', () => ({
  useOtherStore: () => ({ value: 42 })
}))

it('использует mocked другой store', () => {
  const otherStore = useOtherStore()
  expect(otherStore.value).toBe(42)
})

Это позволяет полностью изолировать store и сосредоточиться на его собственной логике.


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

  • Для каждого store создавать отдельный файл с тестами, например counter.spec.js.

  • Использовать beforeEach для сброса состояния и создания нового экземпляра Pinia.

  • Покрывать тестами:

    • начальные состояния,
    • геттеры,
    • actions (синхронные и асинхронные),
    • подписки и побочные эффекты.
  • Мокировать все внешние зависимости для чистоты юнит-тестов.


Тщательное тестирование Pinia stores обеспечивает стабильность логики состояния и позволяет безопасно рефакторить приложение, минимизируя риск ошибок в сложных компонентах Vue.