Files
gt-agent-company/.agents/skills/vue-best-practices/rules/pinia-store-mocking.md
sexygoat 3c62cc1cd1 first
2026-03-31 18:41:52 +08:00

3.7 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Mocking Pinia Stores with Vitest HIGH properly mocks Pinia stores in component tests efficiency pinia, vitest, testing, mock, createTestingPinia, store

Mocking Pinia Stores with Vitest

Impact: HIGH - properly mocks Pinia stores in component tests

Developers struggle to properly mock Pinia stores: createTestingPinia requires explicit createSpy configuration, and "injection Symbol(pinia) not found" errors occur without proper setup.

Important (@pinia/testing 1.0+): The createSpy option is REQUIRED, not optional. Omitting it throws an error: "You must configure the createSpy option."

Symptoms

  • "injection Symbol(pinia) not found" error
  • "You must configure the createSpy option" error
  • Actions not properly mocked
  • Store state not reset between tests

Fix

Pattern 1: Basic setup with createTestingPinia

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { vi } from 'vitest'
import MyComponent from './MyComponent.vue'
import { useCounterStore } from '@/stores/counter'

test('component uses store', async () => {
  const wrapper = mount(MyComponent, {
    global: {
      plugins: [
        createTestingPinia({
          createSpy: vi.fn,  // REQUIRED in @pinia/testing 1.0+
          initialState: {
            counter: { count: 10 }  // Set initial state
          }
        })
      ]
    }
  })

  // Get the store instance AFTER mounting
  const store = useCounterStore()

  // Actions are automatically stubbed
  await wrapper.find('button').trigger('click')
  expect(store.increment).toHaveBeenCalled()
})

Pattern 2: Customize action behavior

test('component handles async action', async () => {
  const wrapper = mount(MyComponent, {
    global: {
      plugins: [
        createTestingPinia({
          createSpy: vi.fn,
          stubActions: false  // Don't stub, use real actions
        })
      ]
    }
  })

  const store = useCounterStore()

  // Override specific action
  store.fetchData = vi.fn().mockResolvedValue({ items: [] })

  await wrapper.find('.load-button').trigger('click')
  expect(store.fetchData).toHaveBeenCalled()
})

Pattern 3: Testing store directly

import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  test('increments count', () => {
    const store = useCounterStore()
    expect(store.count).toBe(0)

    store.increment()
    expect(store.count).toBe(1)
  })
})

Setup Store with Vitest

// stores/counter.ts - Setup store syntax
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

// Test file
test('setup store works', async () => {
  const pinia = createTestingPinia({
    createSpy: vi.fn,
    initialState: {
      counter: { count: 5 }
    }
  })

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

  const store = useCounterStore()
  expect(store.count).toBe(5)
  expect(store.doubleCount).toBe(10)
})

Reset Between Tests

describe('Store Tests', () => {
  let pinia: Pinia

  beforeEach(() => {
    pinia = createTestingPinia({
      createSpy: vi.fn
    })
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  test('test 1', () => { /* ... */ })
  test('test 2', () => { /* ... */ })
})

Reference