All skills
hyf0 avatar

/vue-testing-best-practices

@bc922e4 official
by hyf0hyf0/vue-skills2.9k stars
167

Use for Vue.js testing. Covers Vitest, Vue Test Utils, component testing, mocking, testing patterns, and Playwright for E2E testing.

Use this Skill: https://skilld.dev/gh/hyf0/vue-skills/vue-testing-best-practices

This session only. Nothing lands on disk.

referencetesting-pinia-store-setup.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Configure Pinia Testing with createTestingPinia and setActivePinia

Impact: HIGH - Testing components or composables that use Pinia stores without proper configuration results in "[Vue warn]: injection Symbol(pinia) not found" errors. Tests will fail or behave unexpectedly.

Use @pinia/testing package with createTestingPinia for component tests and setActivePinia(createPinia()) for unit testing stores directly.

Task Checklist

  • Install @pinia/testing as a dev dependency
  • Use createTestingPinia in component tests with global.plugins
  • Use setActivePinia(createPinia()) in beforeEach for store unit tests
  • Configure createSpy: vi.fn when NOT using globals: true in Vitest
  • Initialize store inside each test to get fresh state
  • Use stubActions: false when you need real action execution

Incorrect:

import { mount } from '@vue/test-utils'
import UserProfile from './UserProfile.vue'

// BAD: Missing Pinia - causes injection error
test('displays user name', () => {
  const wrapper = mount(UserProfile)  // ERROR: injection "Symbol(pinia)" not found
  expect(wrapper.text()).toContain('John')
})
import { useUserStore } from '@/stores/user'

// BAD: No active Pinia instance
test('user store actions', () => {
  const store = useUserStore()  // ERROR: no active Pinia
  store.login('john', 'password')
})

Correct - Component Testing:

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { vi } from 'vitest'
import UserProfile from './UserProfile.vue'
import { useUserStore } from '@/stores/user'

// CORRECT: Provide testing pinia with stubbed actions
test('displays user name', () => {
  const wrapper = mount(UserProfile, {
    global: {
      plugins: [
        createTestingPinia({
          createSpy: vi.fn,  // Required if not using globals: true
          initialState: {
            user: { name: 'John', email: 'john@example.com' }
          }
        })
      ]
    }
  })

  expect(wrapper.text()).toContain('John')
})

// CORRECT: Test with stubbed actions (default behavior)
test('calls logout action', async () => {
  const wrapper = mount(UserProfile, {
    global: {
      plugins: [createTestingPinia({ createSpy: vi.fn })]
    }
  })

  // Get store AFTER mounting with createTestingPinia
  const store = useUserStore()

  await wrapper.find('[data-testid="logout"]').trigger('click')

  // Actions are stubbed and wrapped in spies
  expect(store.logout).toHaveBeenCalled()
})

Correct - Store Unit Testing:

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'

describe('User Store', () => {
  beforeEach(() => {
    // Create fresh Pinia instance for each test
    setActivePinia(createPinia())
  })

  it('initializes with empty user', () => {
    const store = useUserStore()
    expect(store.user).toBeNull()
    expect(store.isLoggedIn).toBe(false)
  })

  it('updates user on login', async () => {
    const store = useUserStore()

    // Real action executes - not stubbed
    await store.login('john', 'password')

    expect(store.user).toEqual({ name: 'John' })
    expect(store.isLoggedIn).toBe(true)
  })

  it('clears user on logout', () => {
    const store = useUserStore()
    store.user = { name: 'John' }  // Set initial state

    store.logout()

    expect(store.user).toBeNull()
  })
})

Testing with Real Actions vs Stubbed Actions

import { createTestingPinia } from '@pinia/testing'

// Stubbed actions (default) - for isolation
const wrapper = mount(Component, {
  global: {
    plugins: [
      createTestingPinia({
        createSpy: vi.fn,
        // stubActions: true (default) - actions are mocked
      })
    ]
  }
})

// Real actions - for integration testing
const wrapper = mount(Component, {
  global: {
    plugins: [
      createTestingPinia({
        createSpy: vi.fn,
        stubActions: false  // Actions execute normally
      })
    ]
  }
})

Mocking Specific Action Implementations

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { vi } from 'vitest'
import { useCartStore } from '@/stores/cart'

test('handles checkout failure', async () => {
  const wrapper = mount(Checkout, {
    global: {
      plugins: [createTestingPinia({ createSpy: vi.fn })]
    }
  })

  const cartStore = useCartStore()

  // Mock specific action behavior
  cartStore.checkout.mockRejectedValue(new Error('Payment failed'))

  await wrapper.find('[data-testid="checkout"]').trigger('click')
  await flushPromises()

  expect(wrapper.find('.error').text()).toContain('Payment failed')
})

Spying on Actions with vi.spyOn

import { setActivePinia, createPinia } from 'pinia'
import { vi } from 'vitest'
import { useUserStore } from '@/stores/user'

test('tracks action calls', async () => {
  setActivePinia(createPinia())
  const store = useUserStore()

  const loginSpy = vi.spyOn(store, 'login')
  loginSpy.mockResolvedValue({ success: true })

  await store.login('john', 'password')

  expect(loginSpy).toHaveBeenCalledWith('john', 'password')
})

Testing Store $subscribe

import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'

test('subscription triggers on state change', () => {
  setActivePinia(createPinia())
  const store = useUserStore()

  const callback = vi.fn()
  store.$subscribe(callback)

  store.user = { name: 'John' }

  expect(callback).toHaveBeenCalled()
})

Reference

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill provides standard documentation, configuration guidelines, and best practices for testing Vue 3 applications with Vitest, Vue Test Utils, and Playwright. No security vulnerabilities or malicious patterns were detected.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    1/12 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at bc922e4. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 8 months ago
version
1.0.0
author
github.com/vuejs-ai
  • Vue
  • Testing
  • vitest
  • vue-test-utils
  • playwright
  • e2e-testing
  • composables
  • pinia
  • component-testing
  • mocking

README badge

README badge for hyf0/vue-skills/vue-testing-best-practices

Covers Vue 3 testing patterns and infrastructure with Vitest, Vue Test Utils, and Playwright, including component testing, composables, Pinia integration, and E2E testing. Addresses common gotchas like async race conditions, lifecycle hook testing, and Suspense components.

Generated from the current SKILL.md.

Does this skill cover unit testing, component testing, and E2E testing?
Yes. It covers unit testing with Vitest, component testing with Vue Test Utils, composables testing with helper wrappers, and E2E testing with Playwright.
What testing framework does this skill recommend for Vue 3?
Vitest is recommended for unit and component testing. Playwright is recommended for end-to-end testing.
Does this address Pinia store testing?
Yes. The skill includes guidance on setting up Pinia stores in tests and resolving injection Symbol errors.
Does this cover testing async components and Suspense?
Yes. It includes patterns for testing components with async setup, Suspense boundaries, and defineAsyncComponent.
Does this address flaky tests caused by race conditions?
Yes. The skill covers async/await patterns and flushing promises to handle race conditions and intermittent test failures.

Generated from the current SKILL.md. These answers refresh after source changes.