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-async-await-flushpromises.md

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

Properly Handle Async Updates with nextTick and flushPromises

Impact: HIGH - Vue updates the DOM asynchronously. Without properly awaiting these updates, tests may assert against stale DOM state, causing intermittent failures and false negatives.

Use await with triggers and setValue, use nextTick for reactive updates, and use flushPromises for external async operations like API calls.

Task Checklist

  • Always await trigger() and setValue() calls
  • Use await nextTick() after programmatic reactive state changes
  • Use await flushPromises() for external async operations (API calls, timers)
  • Don't chain multiple nextTick calls - use flushPromises instead
  • Consider using waitFor from testing-library for polling assertions

Incorrect:

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

// BAD: Not awaiting trigger - assertion runs before DOM updates
test('search filters results', () => {
  const wrapper = mount(SearchComponent)

  wrapper.find('input').setValue('vue')  // Missing await!
  wrapper.find('button').trigger('click')  // Missing await!

  // This assertion likely fails - DOM hasn't updated yet
  expect(wrapper.findAll('.result').length).toBe(3)
})

// BAD: Using nextTick for API calls
test('loads data from API', async () => {
  const wrapper = mount(DataLoader)

  await nextTick()  // This won't wait for the API call!

  // Assertion runs before fetch completes
  expect(wrapper.find('.data').text()).toBe('Loaded data')
})

Correct:

import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import SearchComponent from './SearchComponent.vue'
import DataLoader from './DataLoader.vue'

// CORRECT: Await trigger and setValue
test('search filters results', async () => {
  const wrapper = mount(SearchComponent)

  await wrapper.find('input').setValue('vue')
  await wrapper.find('button').trigger('click')

  expect(wrapper.findAll('.result').length).toBe(3)
})

// CORRECT: Use flushPromises for API calls
test('loads data from API', async () => {
  const wrapper = mount(DataLoader)

  // Wait for all pending promises to resolve
  await flushPromises()

  expect(wrapper.find('.data').text()).toBe('Loaded data')
})

When to Use Each Method

await trigger() / await setValue() - User Interactions

// These methods return nextTick internally
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('new value')
await wrapper.find('form').trigger('submit')

await nextTick() - Programmatic Reactive Updates

import { nextTick } from 'vue'

test('reflects programmatic state changes', async () => {
  const wrapper = mount(Counter)

  // Direct state modification (when testing with exposed internals)
  wrapper.vm.count = 5

  await nextTick()  // Wait for Vue to update DOM

  expect(wrapper.find('.count').text()).toBe('5')
})

await flushPromises() - External Async Operations

import { flushPromises } from '@vue/test-utils'

test('displays fetched data', async () => {
  const wrapper = mount(UserProfile, {
    props: { userId: 1 }
  })

  // Wait for component's API call to complete
  await flushPromises()

  expect(wrapper.find('.username').text()).toBe('John')
})

// Sometimes you need multiple flushPromises for chained async operations
test('processes data after fetch', async () => {
  const wrapper = mount(DataProcessor)

  await flushPromises()  // Wait for fetch
  await flushPromises()  // Wait for processing triggered by fetch

  expect(wrapper.find('.processed').exists()).toBe(true)
})

Common Pattern: Combining Methods

test('submits form and shows success', async () => {
  const wrapper = mount(ContactForm)

  // Fill form (awaiting each interaction)
  await wrapper.find('#name').setValue('John')
  await wrapper.find('#email').setValue('john@example.com')

  // Submit form
  await wrapper.find('form').trigger('submit')

  // Wait for API submission to complete
  await flushPromises()

  // Assert success state
  expect(wrapper.find('.success-message').exists()).toBe(true)
})

Testing with MSW or Mock APIs

import { flushPromises } from '@vue/test-utils'
import { rest } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
  rest.get('/api/user', (req, res, ctx) => {
    return res(ctx.json({ name: 'John' }))
  })
)

test('displays user data', async () => {
  const wrapper = mount(UserCard)

  // MSW might require multiple flushPromises
  await flushPromises()
  await flushPromises()

  expect(wrapper.find('.name').text()).toBe('John')
})

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.