Test Vue 3 components with Vue Test Utils and Vitest — mount vs shallowMount, finding and triggering DOM, asserting props and emitted events, awaiting async updates, and mocking Pinia stores and Vue Router.
Instrucciones de origen · Vista previa de solo lectura
name
Vue Testing Utils
description
Test Vue 3 components with Vue Test Utils and Vitest — mount vs shallowMount, finding and triggering DOM, asserting props and emitted events, awaiting async updates, and mocking Pinia stores and Vue Router.
This skill makes an AI agent write Vue 3 component tests with @vue/test-utils on Vitest: mounting with props and slots, querying via data-testid, triggering events and awaiting the render queue, asserting emitted() payloads, and wiring createTestingPinia and mocked routers through global.plugins. Trigger it on any Vue 3 + Vite project where components need unit or integration tests.
Core Principles
Default to mount, reach for shallowMount rarely. Stubbing all children tests a skeleton, not the component. Shallow-render only when a child is genuinely heavy (charts, maps) — and stub that one child explicitly instead.
Test the rendered contract: props in, DOM and emitted events out. Never reach into wrapper.vm internals or assert on ref values; those tests survive refactors only by accident.
await every interaction. Vue batches DOM updates; trigger, setValue, and setProps all return promises that resolve after the next tick. A missing await asserts against the stale DOM.
Use data-testid or roles, not class selectors. Tailwind/scoped-CSS classes change with styling work; test IDs change only when behavior does.
Emitted events are the component's API — assert names and payloads.wrapper.emitted('save') returns the calls array; check both that it fired and what it carried.
Real Pinia logic, fake server. With createTestingPinia({ stubActions: false }) plus mocked HTTP you test store-component integration honestly; stubbed actions are for pure-render tests only.
Use wrapper.get() when the element must exist (throws with a clear message); wrapper.find() + .exists() only for asserting absence.
Share mount defaults via a factory: const factory = (props = {}) => mount(Comp, { props: { ...defaults, ...props } }) — not via a mutable module-level wrapper.
Assert emitted() payload equality on the whole calls array (toEqual([[3]])) to catch double-fires for free.
For components using <Teleport>, target the teleport destination with document.querySelector or stub teleport with global.stubs: { teleport: true }.
Test accessibility-relevant output: attributes('aria-expanded'), attributes('disabled') — these are behavior, not styling.
Keep one component per test file mounted fresh per test; restoreMocks: true plus fresh mounts eliminates 90% of cross-test pollution.
Anti-Patterns
wrapper.vm.someRef = 5 to set state. Mutating internals bypasses the component contract; drive state through props, interactions, or store initial state.
Missing await on trigger/setValue/setProps. The assertion sees the previous DOM and passes or fails for the wrong reason.
shallowMount as the default everywhere. Stub names in snapshots (<child-component-stub>) verify nothing about integration.
Mounting the full real router and await router.isReady() for a unit test. Mock $router.push instead; real-router tests belong in a small dedicated navigation suite.
Asserting CSS classes as behavior (expect(wrapper.classes()).toContain('text-red-500')). Assert the state that drives the class (aria-invalid, emitted validation event) instead.
One beforeEach that mounts with a kitchen-sink global config for every test in the file — slot, store, and router config should appear in the tests that need them.
When to Trigger This Skill
A Vue 3 project needs component tests, or @vue/test-utils is in devDependencies.
The user asks how to test props, emits, v-model, slots, or async data fetching in a Vue component.
Tests are flaky from missing await/flushPromises or pollute each other through shared wrappers.
Components depend on Pinia or Vue Router and the user needs them mocked or stubbed in tests.
Migrating Vue 2 (createLocalVue, propsData) tests to the Vue 3 global/props API.