READ-ONLY PACKAGE PREVIEW

pinia/references/best-practices-testing.md

Version e53a142a2420.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.

← Return to resource and package checksum


name: testing description: Unit testing stores and components with @pinia/testing


Testing Stores

Unit Testing Stores

Create a fresh pinia instance for each test:

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

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

  it('increments', () => {
    const counter = useCounterStore()
    expect(counter.n).toBe(0)
    counter.increment()
    expect(counter.n).toBe(1)
  })
})

With Plugins

import { setActivePinia, createPinia } from 'pinia'
import { createApp } from 'vue'
import { somePlugin } from '../src/stores/plugin'

const app = createApp({})

beforeEach(() => {
  const pinia = createPinia().use(somePlugin)
  app.use(pinia)
  setActivePinia(pinia)
})

Testing Components

Install @pinia/testing:

npm i -D @pinia/testing

Use createTestingPinia():

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { useSomeStore } from '@/stores/myStore'

const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia()],
  },
})

const store = useSomeStore()

// Manipulate state directly
store.name = 'new name'
store.$patch({ name: 'new name' })

// Actions are stubbed by default
store.someAction()
expect(store.someAction).toHaveBeenCalledTimes(1)

Initial State

Set initial state for tests:

const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        initialState: {
          counter: { n: 20 }, // Store name → initial state
        },
      }),
    ],
  },
})

Action Stubbing

Stubbing Limitations in Setup Stores

stubActions only replaces actions on the store instance. In a Setup store, an action that calls another action via its closed-over function reference bypasses the stub:

export const useCounterStore = defineStore('counter', () => {
  function increment() { /* ... */ }

  function incrementTwice() {
    increment() // ❌ closed-over reference — stub never used
    increment()
  }

  return { increment, incrementTwice }
})

Stubbing affects store.increment() but not the internal calls in incrementTwice(). Options stores don't have this issue — internal this.increment() calls go through the store instance and hit the stub.

To make an internal call stubbable in a Setup store, route it through the store (overkill just for a test — prefer testing the real implementation instead):

function incrementTwice() {
  const store = useCounterStore()
  store.increment() // ✅ goes through the store, uses the stub
  store.increment()
}

Execute Real Actions

createTestingPinia({ stubActions: false })

Selective Stubbing

// Only stub specific actions
createTestingPinia({
  stubActions: ['increment', 'reset'],
})

// Or use a function
createTestingPinia({
  stubActions: (actionName, store) => {
    if (actionName.startsWith('set')) return true
    return false
  },
})

Mock Action Return Values

import type { Mock } from 'vitest'

// After getting store
store.someAction.mockResolvedValue('mocked value')

Mocking Getters

Getters are writable in tests:

const pinia = createTestingPinia()
const counter = useCounterStore(pinia)

counter.double = 3 // Override computed value

// Reset to default behavior
counter.double = undefined
counter.double // Now computed normally

Custom Spy Function

If not using Jest/Vitest with globals:

import { vi } from 'vitest'

createTestingPinia({
  createSpy: vi.fn,
})

With Sinon:

import sinon from 'sinon'

createTestingPinia({
  createSpy: sinon.spy,
})

Pinia Plugins in Tests

Pass plugins to createTestingPinia():

import { somePlugin } from '../src/stores/plugin'

createTestingPinia({
  stubActions: false,
  plugins: [somePlugin],
})

Don't use testingPinia.use(MyPlugin) - pass plugins in options.

Type-Safe Mocked Store

import type { Mock } from 'vitest'
import type { Store, StoreDefinition } from 'pinia'

function mockedStore<TStoreDef extends () => unknown>(
  useStore: TStoreDef
): TStoreDef extends StoreDefinition<infer Id, infer State, infer Getters, infer Actions>
  ? Store<Id, State, Record<string, never>, {
      [K in keyof Actions]: Actions[K] extends (...args: any[]) => any
        ? Mock<Actions[K]>
        : Actions[K]
    }>
  : ReturnType<TStoreDef> {
  return useStore() as any
}

// Usage
const store = mockedStore(useSomeStore)
store.someAction.mockResolvedValue('value') // Typed!

E2E Tests

No special handling needed - Pinia works normally.