
Vue Testing Best Practices
Write reliable Vitest tests for Vue 3 async components using flushPromises and correct await patterns.
Overview
Vue Testing Best Practices is an agent skill for the Ship phase that teaches how to test Vue 3 async components with flushPromises and Vitest so assertions run after the component has loaded.
Install
npx skills add https://github.com/hyf0/vue-skills --skill vue-testing-best-practicesWhat is this skill?
- HIGH-impact gotcha: assertions before async components load cause false negatives
- 5-item task checklist: async tests, flushPromises after mount, loading/error state coverage, awaited trigger()
- Correct pattern: mount defineAsyncComponent then await flushPromises() before expect()
- Covers loading states (assert before flush) and error states (rejected dynamic import)
- Vitest + @vue/test-utils examples for defineAsyncComponent
- 5-item task checklist for async component testing
- Documented impact level: HIGH for missing flushPromises
Adoption & trust: 1.8k installs on skills.sh; 2.5k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Vitest suite fails or flakes on defineAsyncComponent tests because expectations run before the async chunk loads and Vue finishes updating.
Who is it for?
Solo builders on Vue 3 + Vitest who use defineAsyncComponent and need stable component tests before ship.
Skip if: Teams wanting full E2E Playwright strategy, non-Vue frameworks, or projects with no async component tests.
When should I use this skill?
Testing Vue 3 async components, defineAsyncComponent, flushPromises, or Vitest async assertion timing issues.
What do I get? / Deliverables
Async component tests reliably await resolution, cover loading and error states, and stop producing false negatives in CI.
- Corrected async component test patterns with flushPromises
- Loading and error-state test cases for async widgets
Recommended Skills
Journey fit
How it compares
Use instead of copying synchronous mount/expect patterns from sync components—this is a targeted async-testing checker, not a full test architecture skill.
Common Questions / FAQ
Who is vue-testing-best-practices for?
Indie and solo frontend builders using Vue 3, @vue/test-utils, and Vitest who hit failing tests around lazy-loaded or async components.
When should I use vue-testing-best-practices?
During Ship testing when writing or fixing component specs for defineAsyncComponent, loading UI, rejected imports, or awaited user trigger() flows.
Is vue-testing-best-practices safe to install?
It is documentation-style testing guidance; still review the Security Audits panel on this Prism page and the skill files in your repo before enabling agent shell access in CI.
SKILL.md
READMESKILL.md - Vue Testing Best Practices
# Use flushPromises for Testing Async Components **Impact: HIGH** - When testing async components created with `defineAsyncComponent`, you must use `await flushPromises()` to ensure the component has loaded before making assertions. Vue updates asynchronously, so tests that don't account for this will make assertions before the component has rendered. ## Task Checklist - [ ] Use `async/await` in test functions for async components - [ ] Call `await flushPromises()` after mounting async components - [ ] Test loading states by making assertions before `flushPromises()` - [ ] Test error states using rejected promises in `defineAsyncComponent` - [ ] Use `trigger()` with `await` as it returns a Promise **Incorrect:** ```javascript import { mount } from '@vue/test-utils' import { defineAsyncComponent } from 'vue' const AsyncWidget = defineAsyncComponent(() => import('./Widget.vue') ) test('renders async component', () => { const wrapper = mount(AsyncWidget) // FAILS: Component hasn't loaded yet expect(wrapper.text()).toContain('Widget Content') }) ``` **Correct:** ```javascript import { mount, flushPromises } from '@vue/test-utils' import { defineAsyncComponent, nextTick } from 'vue' const AsyncWidget = defineAsyncComponent(() => import('./Widget.vue') ) test('renders async component', async () => { const wrapper = mount(AsyncWidget) // Wait for async component to load await flushPromises() expect(wrapper.text()).toContain('Widget Content') }) test('shows loading state initially', async () => { const AsyncWithLoading = defineAsyncComponent({ loader: () => import('./Widget.vue'), loadingComponent: { template: '<div>Loading...</div>' }, delay: 0 }) const wrapper = mount(AsyncWithLoading) // Check loading state immediately expect(wrapper.text()).toContain('Loading...') // Wait for component to load await flushPromises() // Check final state expect(wrapper.text()).toContain('Widget Content') }) ``` ## Testing with Suspense ```javascript import { mount, flushPromises } from '@vue/test-utils' import { Suspense, defineAsyncComponent, h } from 'vue' const AsyncWidget = defineAsyncComponent(() => import('./Widget.vue') ) test('renders async component with Suspense', async () => { const wrapper = mount({ components: { AsyncWidget }, template: ` <Suspense> <AsyncWidget /> <template #fallback> <div>Loading...</div> </template> </Suspense> ` }) // Initially shows fallback expect(wrapper.text()).toContain('Loading...') // Wait for async resolution await flushPromises() // Now shows actual content expect(wrapper.text()).toContain('Widget Content') }) ``` ## Testing Error States ```javascript import { mount, flushPromises } from '@vue/test-utils' import { defineAsyncComponent } from 'vue' test('shows error component on load failure', async () => { const AsyncWithError = defineAsyncComponent({ loader: () => Promise.reject(new Error('Failed to load')), errorComponent: { template: '<div>Error loading component</div>' } }) const wrapper = mount(AsyncWithError) await flushPromises() expect(wrapper.text()).toContain('Error loading component') }) ``` ## Utilities Reference | Utility | Purpose | |---------|---------| | `await flushPromises()` | Resolves all pending promises | | `await nextTick()` | Waits for Vue's next DOM update cycle | | `await wrapper.trigger('click')` | Triggers event and waits for update | ## Dynamic Import Handling **Note:** Dynamic imports (`import('./File.vue')`) may require additional handling beyond `flushPromises()` in test environments. Test runners like Vitest