---
title: "Testing Pinia stores with createTestingPinia · vuejs.school"
description: "Test a Pinia store on its own with a fresh pinia, then test a component with createTestingPinia, stubbed actions, initial state and the getter gotcha."
canonical_url: "https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia"
meta:
  "og:description": "Test a Pinia store on its own with a fresh pinia, then test a component with createTestingPinia, stubbed actions, initial state and the getter gotcha."
  "og:image": "https://vuejs.school/_og/d/a_~VGVzdGluZyBQaW5pYSBzdG9yZXMgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Testing%2BPinia%2Bstores%2Bwith%2BcreateTestingPinia%2Cdescription_~VGVzdCBhIFBpbmlhIHN0b3JlIG9uIGl0cyBvd24gd2l0aCBhIGZyZXNoIHBpbmlhLCB0aGVuIHRlc3QgYSBjb21wb25lbnQgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEsIHN0dWJiZWQgYWN0aW9ucywgaW5pdGlhbCBzdGF0ZSBhbmQgdGhlIGdldHRlciBnb3RjaGEu%2Ceyebrow_~VHV0b3JpYWwgwrcgNiBtaW4gcmVhZA%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy90ZXN0aW5nLXBpbmlhLXN0b3Jlcy13aXRoLWNyZWF0ZXRlc3RpbmdwaW5pYSI%2Cs_7TCyYWYwG9h2Deyk.png"
  "og:image:alt": "Testing Pinia stores with createTestingPinia · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Testing Pinia stores with createTestingPinia · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~VGVzdGluZyBQaW5pYSBzdG9yZXMgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Testing%2BPinia%2Bstores%2Bwith%2BcreateTestingPinia%2Cdescription_~VGVzdCBhIFBpbmlhIHN0b3JlIG9uIGl0cyBvd24gd2l0aCBhIGZyZXNoIHBpbmlhLCB0aGVuIHRlc3QgYSBjb21wb25lbnQgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEsIHN0dWJiZWQgYWN0aW9ucywgaW5pdGlhbCBzdGF0ZSBhbmQgdGhlIGdldHRlciBnb3RjaGEu%2Ceyebrow_~VHV0b3JpYWwgwrcgNiBtaW4gcmVhZA%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy90ZXN0aW5nLXBpbmlhLXN0b3Jlcy13aXRoLWNyZWF0ZXRlc3RpbmdwaW5pYSI%2Cs_7TCyYWYwG9h2Deyk.png"
  "twitter:image:alt": "Testing Pinia stores with createTestingPinia · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## The cart store and the component that uses it

The examples live in a project made with create-vue, with Vitest and jsdom. create-vue maps `@` to `src/`, so `@/stores/cart` below means `src/stores/cart.ts`. It also leaves Vitest's `globals` option off, so each test file imports the helpers it uses, such as `describe`, `it` and `vi`, from `vitest`. That matters for the `createSpy` option later.

The store is a setup store with two getters, `count` and `total`, and two actions, `add` and `clear`:

*src/stores/cart.ts*

```
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
}

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])

  const count = computed(() => items.value.reduce((sum, i) => sum + i.quantity, 0))
  const total = computed(() => items.value.reduce((sum, i) => sum + i.price * i.quantity, 0))

  function add(item: Omit<CartItem, 'quantity'>) {
    const existing = items.value.find(i => i.id === item.id)
    if (existing) existing.quantity++
    else items.value.push({ ...item, quantity: 1 })
  }

  function clear() {
    items.value = []
  }

  return { items, count, total, add, clear }
})
```

`CartSummary` reads the two getters through `storeToRefs()` and calls `clear()` when the button is pressed:

*src/components/CartSummary.vue*

```
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()
const { count, total } = storeToRefs(cart)
</script>

<template>
  <p data-test="summary">{{ count }} items, {{ total.toFixed(2) }}</p>
  <button type="button" @click="cart.clear()">Empty cart</button>
</template>
```

## Test the store on its own

A store cannot work without a pinia instance. In a component test the app provides one. In a plain store test you make one yourself and mark it active, so that `useCartStore()` finds it without being passed anything:

*src/stores/\_\_tests\_\_/cart.spec.ts*

```
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it } from 'vitest'
import { useCartStore } from '../cart'

describe('cart store', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('adds a new item with quantity 1', () => {
    const cart = useCartStore()
    cart.add({ id: 1, name: 'Mug', price: 12 })
    expect(cart.items).toHaveLength(1)
    expect(cart.count).toBe(1)
  })

  it('increments the quantity when the same item is added twice', () => {
    const cart = useCartStore()
    cart.add({ id: 1, name: 'Mug', price: 12 })
    cart.add({ id: 1, name: 'Mug', price: 12 })
    expect(cart.items).toHaveLength(1)
    expect(cart.total).toBe(24)
  })
})
```

The `beforeEach` is the whole trick. `createPinia()` makes a fresh, empty instance and `setActivePinia()` makes it the one every `useStore()` call uses. Because it runs before each test, the second test starts with an empty cart no matter what the first did. Create the pinia once for the whole file instead, and the two tests share one store: the second starts with the first test's mug already in the cart.

These tests are fast and need no DOM. They are the right place for every branch of an action and every calculation in a getter.

If your store relies on a Pinia plugin, the test needs an app as well. As the [Pinia testing cookbook](https://pinia.vuejs.org/cookbook/testing.html) points out, plugins only run once the pinia is installed in an app. Create one app for the file with `createApp({})`. Then in `beforeEach`, create the pinia with your plugin, `createPinia().use(somePlugin)`, and call `app.use(pinia)` before `setActivePinia(pinia)`.

## Test a component with createTestingPinia

For the component you do not want the real store logic. You want to put the store in a known state, render, and check that the component asked the store to do the right thing. That is what `@pinia/testing` is for:

```
npm install -D @pinia/testing
```

That installs 1.0.1, published on 9 April. It fixes a bug where `storeToRefs()` in a test computed the store's getters straight away, even ones nothing reads, which a pinia from `createPinia()` does not do. `CartSummary` uses `storeToRefs()`, so check that an older install is not still on 1.0.0.

The test file has two tests: one checks what the component shows for a given cart, the other checks that the button asks the store to empty it.

*src/components/\_\_tests\_\_/CartSummary.spec.ts*

```
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import { useCartStore } from '@/stores/cart'
import CartSummary from '../CartSummary.vue'

describe('CartSummary', () => {
  it('shows the count and total from the store', () => {
    const wrapper = mount(CartSummary, {
      global: {
        plugins: [
          createTestingPinia({
            createSpy: vi.fn,
            initialState: {
              cart: { items: [{ id: 1, name: 'Mug', price: 12, quantity: 2 }] },
            },
          }),
        ],
      },
    })

    expect(wrapper.get('[data-test="summary"]').text()).toBe('2 items, 24.00')
  })

  it('asks the store to clear the cart', async () => {
    const wrapper = mount(CartSummary, {
      global: {
        plugins: [
          createTestingPinia({
            createSpy: vi.fn,
            initialState: { cart: { items: [{ id: 1, name: 'Mug', price: 12, quantity: 2 }] } },
          }),
        ],
      },
    })
    const cart = useCartStore()

    await wrapper.get('button').trigger('click')

    expect(cart.clear).toHaveBeenCalledTimes(1)
    expect(cart.items).toHaveLength(1)
  })
})
```

Three things in this file do the work.

`createTestingPinia()` returns a pinia instance made for tests, and it goes in `global.plugins` so the mounted component installs it like the real one. It also becomes the active pinia, which is why `useCartStore()` called after `mount` in the second test returns the same store the component is using.

`createSpy: vi.fn` tells the testing pinia which function makes its spies. With Jest, or with Vitest when `globals: true` is set, it finds one on its own. With `globals` off, as in a create-vue project, `createTestingPinia()` throws as soon as it runs: "You must configure the `createSpy` option". Pass the function itself, `vi.fn`. Calling it, `vi.fn()`, hands over a single spy instead, and 1.0 rejects that with "Invalid `createSpy` option".

`initialState` is an object keyed by store id. Each entry is patched onto the store the moment it is created, before the component renders, so both tests start with two mugs already in the cart. The keys inside are the names you returned from the setup function, `items` here, and the getters compute from them as usual, which is why the summary reads `2 items, 24.00`.

## Stubbed actions, and when to turn them off

By default every action on a testing pinia is replaced with a spy that does nothing. That is why the second test can check that `cart.clear` was called once and also that the two mugs are still in the cart: the click reached the store, but the real `clear` never ran. For a component test this is what you want. The component's job is to call `clear` when the button is pressed. Whether `clear` empties the array is the store test's job: add a case for it next to the two `add` tests.

Sometimes you do want the real actions, for example when a component shows the result of an action in the same test. Set `stubActions: false`. These lines go inside a third, `async` test in the same file:

```
const wrapper = mount(CartSummary, {
  global: {
    plugins: [
      createTestingPinia({
        createSpy: vi.fn,
        stubActions: false,
        initialState: { cart: { items: [{ id: 1, name: 'Mug', price: 12, quantity: 2 }] } },
      }),
    ],
  },
})

await wrapper.get('button').trigger('click')

expect(wrapper.get('[data-test="summary"]').text()).toBe('0 items, 0.00')
```

The actions still run through a spy, so `toHaveBeenCalledTimes` keeps working; they just also execute. In 1.0 `stubActions` is a boolean: either every action is stubbed or none is. If one action has to change state while the rest stay stubbed, keep the default and give that one stub an implementation of your own: `vi.spyOn(cart, 'add').mockImplementation(...)`. Your function replaces the store's `add`; the real one still does not run.

## Set up state without calling a stubbed action

Getters are not stubbed: they compute from state exactly as in the app. That leads to a common mistake:

1. Mount with the default stubbed actions.
2. Call `cart.add(...)` in the test to put an item in the cart.
3. Expect `cart.count` to be 1. It is 0.

The action was a stub, so the state never changed, and the getter correctly reports an empty cart. When the actions are stubs, set up state through `initialState`, or assign to the store directly: `cart.items = [...]` works because state on a testing pinia is writable like any other.

## Force a getter's value in a test

When a test needs a getter to return a fixed value, assign one: getters on a testing pinia are writable. `cart.total = 99` makes the summary show `99.00` whatever the items are. This works for the `computed()` values of a setup store as well as the `getters` of an option store. TypeScript still types getters as read-only, so in a `.ts` test file put a `// @ts-expect-error` comment above the assignment.

To undo an override, the cookbook sets the getter back to `undefined`. That works while only the test reads the getter, but not while a component is showing it: in `CartSummary` the reset leaves `total` as `undefined`, and the template's `toFixed()` throws on the next render. Override a getter in a test of its own instead. Each test creates a new testing pinia, so the override ends with the test.

## Decide what each test covers

Put the logic in the store, and test it with `setActivePinia(createPinia())`: every branch of `add`, every number `total` can produce, every edge case, in a file with no DOM. Then test each component with `createTestingPinia`, with state set through `initialState` and actions left as stubs. Assert two things: what the component rendered from the state, and which action it called with which arguments.

**Check it stuck**3 questions from the Testing Pinia stores quiz.

[**Practice now**](https://vuejs.school/learn/testing/testing-pinia-stores?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 6 min read**Vitest 5: what changed for Vue tests**](https://vuejs.school/community/blog/vitest-5-what-changed-for-vue-tests) [Release notes · 6 min read**Vitest 5 release candidate: what's coming for Vue tests**](https://vuejs.school/community/blog/vitest-5-rc-whats-coming)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-04-10","description":"Test a Pinia store on its own with a fresh pinia, then test a component with createTestingPinia, stubbed actions, initial state and the getter gotcha.","name":"Testing Pinia stores with createTestingPinia","url":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia","breadcrumb":{"@id":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-04-10","description":"Test a Pinia store on its own with a fresh pinia, then test a component with createTestingPinia, stubbed actions, initial state and the getter gotcha.","headline":"Testing Pinia stores with createTestingPinia","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~VGVzdGluZyBQaW5pYSBzdG9yZXMgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Testing%2BPinia%2Bstores%2Bwith%2BcreateTestingPinia%2Cdescription_~VGVzdCBhIFBpbmlhIHN0b3JlIG9uIGl0cyBvd24gd2l0aCBhIGZyZXNoIHBpbmlhLCB0aGVuIHRlc3QgYSBjb21wb25lbnQgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEsIHN0dWJiZWQgYWN0aW9ucywgaW5pdGlhbCBzdGF0ZSBhbmQgdGhlIGdldHRlciBnb3RjaGEu%2Ceyebrow_~VHV0b3JpYWwgwrcgNiBtaW4gcmVhZA%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy90ZXN0aW5nLXBpbmlhLXN0b3Jlcy13aXRoLWNyZWF0ZXRlc3RpbmdwaW5pYSI%2Cs_7TCyYWYwG9h2Deyk.png","@type":["Article","BlogPosting"],"articleSection":["Tutorial"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/testing-pinia-stores-with-createtestingpinia#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Testing Pinia stores with createTestingPinia","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~VGVzdGluZyBQaW5pYSBzdG9yZXMgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Testing%2BPinia%2Bstores%2Bwith%2BcreateTestingPinia%2Cdescription_~VGVzdCBhIFBpbmlhIHN0b3JlIG9uIGl0cyBvd24gd2l0aCBhIGZyZXNoIHBpbmlhLCB0aGVuIHRlc3QgYSBjb21wb25lbnQgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEsIHN0dWJiZWQgYWN0aW9ucywgaW5pdGlhbCBzdGF0ZSBhbmQgdGhlIGdldHRlciBnb3RjaGEu%2Ceyebrow_~VHV0b3JpYWwgwrcgNiBtaW4gcmVhZA%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy90ZXN0aW5nLXBpbmlhLXN0b3Jlcy13aXRoLWNyZWF0ZXRlc3RpbmdwaW5pYSI%2Cs_7TCyYWYwG9h2Deyk.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~VGVzdGluZyBQaW5pYSBzdG9yZXMgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Testing%2BPinia%2Bstores%2Bwith%2BcreateTestingPinia%2Cdescription_~VGVzdCBhIFBpbmlhIHN0b3JlIG9uIGl0cyBvd24gd2l0aCBhIGZyZXNoIHBpbmlhLCB0aGVuIHRlc3QgYSBjb21wb25lbnQgd2l0aCBjcmVhdGVUZXN0aW5nUGluaWEsIHN0dWJiZWQgYWN0aW9ucywgaW5pdGlhbCBzdGF0ZSBhbmQgdGhlIGdldHRlciBnb3RjaGEu%2Ceyebrow_~VHV0b3JpYWwgwrcgNiBtaW4gcmVhZA%2Ctint_mint%2Cp_Ii9jb21tdW5pdHkvYmxvZy90ZXN0aW5nLXBpbmlhLXN0b3Jlcy13aXRoLWNyZWF0ZXRlc3RpbmdwaW5pYSI%2Cs_7TCyYWYwG9h2Deyk.png"}]}
```