web-testing-vue-test-utils
Vue Test Utilsを使ってVueコンポーネントのテストを効率的に行うための、mountやshallowMountなどのAPI操作、イベント発火、非同期処理対応、Piniaストアのモック化といった実践的な手法を習得するSkill。
📜 元の英語説明(参考)
Vue Test Utils patterns - mount, shallowMount, wrapper API, trigger, setValue, flushPromises, testing composables, Pinia store mocking
🇯🇵 日本人クリエイター向け解説
Vue Test Utilsを使ってVueコンポーネントのテストを効率的に行うための、mountやshallowMountなどのAPI操作、イベント発火、非同期処理対応、Piniaストアのモック化といった実践的な手法を習得するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o web-testing-vue-test-utils.zip https://jpskill.com/download/10315.zip && unzip -o web-testing-vue-test-utils.zip && rm web-testing-vue-test-utils.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10315.zip -OutFile "$d\web-testing-vue-test-utils.zip"; Expand-Archive "$d\web-testing-vue-test-utils.zip" -DestinationPath $d -Force; ri "$d\web-testing-vue-test-utils.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
web-testing-vue-test-utils.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
web-testing-vue-test-utilsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Vue Test Utils のパターン
クイックガイド: Vue コンポーネントのテストには、完全なレンダリングには
mount()を、分離にはshallowMount()を使用します。data-test属性を持つ要素の検索にはwrapper.find()を、イベントの発火にはawait trigger()を、入力要素への値の設定にはawait setValue()を使用します。非同期処理にはflushPromises()を使用します。Pinia ストアのモックにはcreateTestingPinia()を使用します。
<critical_requirements>
重要: この Skill を使用する前に
すべてのコードは
CLAUDE.mdのプロジェクト規約に従う必要があります (kebab-case, 名前付きエクスポート, インポート順,import type, 名前付き定数)
(すべての DOM 更新メソッド (trigger(), setValue(), setProps(), setData()) は await する必要があります)
(Vue が追跡しない非同期処理 (API 呼び出し、タイマー) の後には flushPromises() を使用する必要があります)
(デフォルトでは mount() を使用する必要があります - パフォーマンス上の問題や複雑な分離が必要な場合にのみ shallowMount() を使用してください)
(セレクターには data-test 属性を使用する必要があります。クラスや ID は使用しないでください)
(Pinia ストアには createTestingPinia() を使用する必要があります。手動でのモックは使用しないでください)
</critical_requirements>
自動検出: Vue Test Utils, @vue/test-utils, mount, shallowMount, wrapper, VueWrapper, DOMWrapper, trigger, setValue, setProps, setData, flushPromises, findComponent, findAllComponents, createTestingPinia, @pinia/testing
使用場面:
- Composition API を使用した Vue 3 コンポーネントのテスト
- ユーザーインタラクションを通じたコンポーネントの動作のテスト
- Pinia ストアを使用するコンポーネントのテスト
- Vue Router を使用するコンポーネントのテスト
- コンポーザブルの単独テスト
- 分離のための子コンポーネントのモック
使用しない場面:
- 複数ページにわたる E2E テスト (E2E ソリューションを使用)
- Vue を使用しない純粋なユーティリティ関数のテスト (直接ユニットテストを使用)
- ビジュアルリグレッションテスト (ビジュアルテストツールを使用)
- テストランナーの設定 (コンポーネントテストとは別の関心事)
カバーする主要なパターン:
- コンポーネントのマウント (mount vs shallowMount)
- Wrapper API (find, findAll, trigger, setValue)
- 非同期テスト (flushPromises, nextTick)
- コンポーザブルのテスト
- createTestingPinia を使用した Pinia ストアのモック
- Vue Router のモック
- 子コンポーネントのスタブ
詳細なリソース:
- コード例については、
examples/フォルダを参照してください。- examples/core.md - マウント、Wrapper API、クエリ
- examples/async.md - flushPromises, nextTick, 非同期セットアップ
- examples/composables.md - コンポーザブルのテスト
- examples/mocking.md - Pinia ストア、Vue Router、スタブ
- 意思決定フレームワークとアンチパターンについては、reference.md を参照してください。
<philosophy>
哲学
Vue Test Utils は、Vue コンポーネントを分離してマウントし、Wrapper API を通じて操作するためのユーティリティを提供します。指針となる原則は、「テストがソフトウェアの使用方法に似ているほど、より確信が得られる」です。
コア原則:
- 実装ではなくユーザーの行動をテストする: 内部コンポーネントの状態ではなく、ユーザーが見て操作するものをテストします。
- 完全なレンダリングを優先する: 現実的なテストのために、デフォルトでは
mount()を使用します。shallowMount()は必要な場合にのみ使用します。 - アクセス可能なセレクターを使用する: 変更される可能性のあるクラスや ID ではなく、
data-test属性を優先します。 - 非同期処理を待機する: Vue は DOM を非同期的に更新します - 常に DOM 更新メソッドを待機します。
- スタブを最小限に抑える: スタブが多いほどテストの忠実度が低下します。必要なものだけをスタブします。
Vue Test Utils の使用場面:
- 子コンポーネントとのコンポーネント統合テスト
- ユーザーインタラクションフローのテスト (クリック、入力、フォーム送信)
- リアクティブな状態と算出プロパティのテスト
- コンポーネントのライフサイクルとウォッチャーのテスト
- Pinia または Vue Router を使用するコンポーネントのテスト
使用しない場面:
- 完全なユーザー Journey テスト (E2E テストを使用)
- 視覚的な外観のテスト (ビジュアルリグレッションツールを使用)
- API エンドポイントの直接テスト (モックされたレスポンスでコンポーネントの動作をテスト)
- サードパーティライブラリの動作のテスト (ライブラリのテストを信頼)
</philosophy>
<patterns>
コアパターン
パターン 1: コンポーネントのマウント
子コンポーネントを含む完全なレンダリングには mount() を使用します。分離が必要な場合や、パフォーマンス上の問題がある場合にのみ shallowMount() を使用します。
mount vs shallowMount
import { mount, shallowMount } from "@vue/test-utils";
import { TodoList } from "./todo-list.vue";
// GOOD: 現実的なテストのために mount() を使用
const wrapper = mount(TodoList, {
props: {
todos: [{ id: 1, text: "Test", done: false }],
},
});
// 使用場面: 子コンポーネントを含むコンポーネントをテストする必要がある場合
// 子コンポーネントは正常にレンダリングされ、スロットは機能し、イベントはバブルアップします
// 分離のための shallowMount() (控えめに使用)
const shallowWrapper = shallowMount(TodoList, {
props: {
todos: [{ id: 1, text: "Test", done: false }],
},
});
// 使用場面: コンポーネントに多数の重い子コンポーネントがある場合、または完全な分離が必要な場合
// すべての子コンポーネントはスタブに置き換えられます
デフォルトで mount を使用する理由: shallowMount はコンポーネントの動作を本番環境とは異なるものにします。子コンポーネントはレンダリングされず、スロットが正しく機能しない可能性があり、統合カバレッジが失われます。
完全なマウントの例については、examples/core.md を参照してください。
パターン 2: クエリのための Wrapper API
要素が必ず存在する必要がある場合は get() を使用します (失敗するとエラーをスローします)。要素が存在しない可能性がある場合は find() を使用します (空の Wrapper を返します)。複数の要素には findAll() を使用します。
要素のクエリ
import { mount } from "@vue/test-utils";
import { SearchForm } from "./search-form.vue";
const DATA_TEST_INPUT = '[data-test="search-input"]';
const DATA_TEST_BUTTON = '[data-test="search-button"]';
const DATA_TEST_RESULT = '[data-test="search-result"]';
test("renders search form elements", () => {
const wrapper = mount(SearchForm);
// get() - 見つからない場合はエラーをスローします (必須要素に使用)
const input = wrapper.get(DATA_TEST_INPUT);
const button = wrapper.get(DATA_TEST_BUTTON);
expect(input.exists()).toBe(true);
expect(button.text()).toBe("Search");
});
test("shows res 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Vue Test Utils Patterns
Quick Guide: Test Vue components with
mount()for full rendering orshallowMount()for isolation. Usewrapper.find()with data-test attributes,await trigger()for events,await setValue()for inputs. UseflushPromises()for async operations. Mock Pinia stores withcreateTestingPinia().
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST await all DOM-updating methods: trigger(), setValue(), setProps(), setData())
(You MUST use flushPromises() after async operations that Vue doesn't track (API calls, timers))
(You MUST use mount() by default - only use shallowMount() for performance issues or complex isolation)
(You MUST use data-test attributes for selectors, NOT classes or IDs)
(You MUST use createTestingPinia() for Pinia stores, NOT manual mocking)
</critical_requirements>
Auto-detection: Vue Test Utils, @vue/test-utils, mount, shallowMount, wrapper, VueWrapper, DOMWrapper, trigger, setValue, setProps, setData, flushPromises, findComponent, findAllComponents, createTestingPinia, @pinia/testing
When to use:
- Testing Vue 3 components with Composition API
- Testing component behavior through user interactions
- Testing components with Pinia stores
- Testing components with Vue Router
- Testing composables in isolation
- Mocking child components for isolation
When NOT to use:
- E2E testing spanning multiple pages (use your E2E solution)
- Testing pure utility functions without Vue (use unit tests directly)
- Visual regression testing (use visual testing tools)
- Test runner configuration (separate concern from component testing)
Key patterns covered:
- Mounting components (mount vs shallowMount)
- Wrapper API (find, findAll, trigger, setValue)
- Async testing (flushPromises, nextTick)
- Testing composables
- Pinia store mocking with createTestingPinia
- Vue Router mocking
- Stubbing child components
Detailed Resources:
- For code examples, see
examples/folder:- examples/core.md - Mounting, wrapper API, queries
- examples/async.md - flushPromises, nextTick, async setup
- examples/composables.md - Testing composables
- examples/mocking.md - Pinia stores, Vue Router, stubs
- For decision frameworks and anti-patterns, see reference.md
<philosophy>
Philosophy
Vue Test Utils provides utilities to mount Vue components in isolation and interact with them through the wrapper API. The guiding principle: "The more your tests resemble the way your software is used, the more confidence they can give you."
Core Principles:
- Test User Behavior, Not Implementation: Test what users see and interact with, not internal component state
- Prefer Full Rendering: Use
mount()by default for realistic testing;shallowMount()only when needed - Use Accessible Selectors: Prefer
data-testattributes over classes or IDs that may change - Await Async Operations: Vue updates the DOM asynchronously - always await DOM-updating methods
- Minimize Stubbing: More stubs reduce test fidelity; stub only what's necessary
When to use Vue Test Utils:
- Component integration testing with child components
- Testing user interaction flows (clicks, input, form submission)
- Testing reactive state and computed properties
- Testing component lifecycle and watchers
- Testing components with Pinia or Vue Router
When NOT to use:
- Full user journey testing (use E2E tests)
- Testing visual appearance (use visual regression tools)
- Testing API endpoints directly (test component behavior with mocked responses)
- Testing third-party library behavior (trust the library's tests)
</philosophy>
<patterns>
Core Patterns
Pattern 1: Mounting Components
Use mount() for full rendering with child components. Use shallowMount() only when you need isolation or have performance issues.
mount vs shallowMount
import { mount, shallowMount } from "@vue/test-utils";
import { TodoList } from "./todo-list.vue";
// GOOD: mount() for realistic testing
const wrapper = mount(TodoList, {
props: {
todos: [{ id: 1, text: "Test", done: false }],
},
});
// Use when: You need to test component with its children
// Child components render normally, slots work, events bubble up
// shallowMount() for isolation (use sparingly)
const shallowWrapper = shallowMount(TodoList, {
props: {
todos: [{ id: 1, text: "Test", done: false }],
},
});
// Use when: Component has many heavy children, or you need complete isolation
// All child components are replaced with stubs
Why mount by default: shallowMount makes the component behave differently from production. Child components don't render, slots may not work correctly, and you lose integration coverage.
See examples/core.md for complete mounting examples.
Pattern 2: Wrapper API for Querying
Use get() when element must exist (throws on failure). Use find() when element may not exist (returns empty wrapper). Use findAll() for multiple elements.
Querying Elements
import { mount } from "@vue/test-utils";
import { SearchForm } from "./search-form.vue";
const DATA_TEST_INPUT = '[data-test="search-input"]';
const DATA_TEST_BUTTON = '[data-test="search-button"]';
const DATA_TEST_RESULT = '[data-test="search-result"]';
test("renders search form elements", () => {
const wrapper = mount(SearchForm);
// get() - throws if not found (use for required elements)
const input = wrapper.get(DATA_TEST_INPUT);
const button = wrapper.get(DATA_TEST_BUTTON);
expect(input.exists()).toBe(true);
expect(button.text()).toBe("Search");
});
test("shows results after search", async () => {
const wrapper = mount(SearchForm);
// find() - returns empty wrapper if not found (use for conditional elements)
expect(wrapper.find(DATA_TEST_RESULT).exists()).toBe(false);
// After triggering search...
await wrapper.get(DATA_TEST_INPUT).setValue("test");
await wrapper.get(DATA_TEST_BUTTON).trigger("click");
// findAll() - returns array of wrappers
const results = wrapper.findAll(DATA_TEST_RESULT);
expect(results.length).toBeGreaterThan(0);
});
Why data-test attributes: CSS classes and IDs change during styling updates. data-test attributes are explicit testing contracts that don't affect production styling.
Pattern 3: User Interactions with trigger and setValue
Always await DOM-updating methods. They return a Promise that resolves after Vue updates the DOM.
Form Interactions
import { mount } from "@vue/test-utils";
import { LoginForm } from "./login-form.vue";
const DATA_TEST_EMAIL = '[data-test="email-input"]';
const DATA_TEST_PASSWORD = '[data-test="password-input"]';
const DATA_TEST_FORM = '[data-test="login-form"]';
const DATA_TEST_ERROR = '[data-test="error-message"]';
const VALID_EMAIL = "test@example.com";
const VALID_PASSWORD = "password123";
test("submits login form", async () => {
const wrapper = mount(LoginForm);
// setValue() for input values - MUST await
await wrapper.get(DATA_TEST_EMAIL).setValue(VALID_EMAIL);
await wrapper.get(DATA_TEST_PASSWORD).setValue(VALID_PASSWORD);
// trigger() for events - MUST await
await wrapper.get(DATA_TEST_FORM).trigger("submit.prevent");
// Assert emitted events
expect(wrapper.emitted("submit")).toBeTruthy();
expect(wrapper.emitted("submit")![0]).toEqual([
{ email: VALID_EMAIL, password: VALID_PASSWORD },
]);
});
test("shows validation errors", async () => {
const wrapper = mount(LoginForm);
// Submit empty form
await wrapper.get(DATA_TEST_FORM).trigger("submit.prevent");
// Check for error message
expect(wrapper.get(DATA_TEST_ERROR).text()).toContain("Email is required");
});
Why await: Vue updates the DOM asynchronously. Without await, assertions run before Vue finishes updating, causing flaky tests.
Pattern 4: Async Operations with flushPromises
Use flushPromises() after async operations that Vue doesn't track (API calls, setTimeout, etc.). Use nextTick() only for reactive state updates.
Testing Async API Calls
import { mount, flushPromises } from "@vue/test-utils";
import { UserProfile } from "./user-profile.vue";
import { vi } from "vitest";
const DATA_TEST_LOADING = '[data-test="loading"]';
const DATA_TEST_NAME = '[data-test="user-name"]';
const DATA_TEST_ERROR = '[data-test="error"]';
const MOCK_USER = { id: 1, name: "John Doe" };
// Mock API module
vi.mock("@/api/users", () => ({
fetchUser: vi.fn(),
}));
import { fetchUser } from "@/api/users";
test("displays user data after loading", async () => {
// Arrange: Setup mock to resolve
vi.mocked(fetchUser).mockResolvedValue(MOCK_USER);
const wrapper = mount(UserProfile, {
props: { userId: 1 },
});
// Initially shows loading
expect(wrapper.find(DATA_TEST_LOADING).exists()).toBe(true);
// Act: Wait for all promises to resolve
await flushPromises();
// Assert: Loading gone, data displayed
expect(wrapper.find(DATA_TEST_LOADING).exists()).toBe(false);
expect(wrapper.get(DATA_TEST_NAME).text()).toBe("John Doe");
});
test("displays error on API failure", async () => {
// Arrange: Setup mock to reject
vi.mocked(fetchUser).mockRejectedValue(new Error("Network error"));
const wrapper = mount(UserProfile, {
props: { userId: 1 },
});
// Act: Wait for promise to reject
await flushPromises();
// Assert: Error displayed
expect(wrapper.get(DATA_TEST_ERROR).text()).toContain("Network error");
});
Why flushPromises: Vue's reactivity system doesn't track external promises (HTTP requests, timers). flushPromises() resolves all pending promises so the DOM reflects the async result.
See examples/async.md for complete async testing examples.
Pattern 5: Testing Components with findComponent/getComponent
Use findComponent() or getComponent() to interact with child Vue components directly. Useful for testing component communication.
getComponent()- Throws error if not found (use when component must exist)findComponent()- Returns empty wrapper if not found (use when component may not exist)
Component Queries
import { mount } from "@vue/test-utils";
import { ParentComponent } from "./parent-component.vue";
import { ChildComponent } from "./child-component.vue";
test("passes props to child component", () => {
const wrapper = mount(ParentComponent, {
props: { message: "Hello" },
});
// getComponent() - throws if not found (clearer error messages)
const child = wrapper.getComponent(ChildComponent);
// Assert props passed correctly
expect(child.props("message")).toBe("Hello");
});
test("receives emitted events from child", async () => {
const wrapper = mount(ParentComponent);
const child = wrapper.getComponent(ChildComponent);
// Trigger event on child
await child.vm.$emit("update", "new value");
// Assert parent handled the event
expect(wrapper.vm.value).toBe("new value");
});
test("conditionally rendered child component", () => {
const wrapper = mount(ParentComponent, {
props: { showChild: false },
});
// findComponent() - returns empty wrapper (check with .exists())
const child = wrapper.findComponent(ChildComponent);
expect(child.exists()).toBe(false);
});
test("finds component by name", () => {
const wrapper = mount(ParentComponent);
// Find by component name (less preferred - use component definition)
const child = wrapper.findComponent({ name: "ChildComponent" });
expect(child.exists()).toBe(true);
});
When to use getComponent vs findComponent:
- Use
getComponentwhen the child must exist - provides clearer error messages on failure - Use
findComponentwhen the child may not exist - check with.exists()first
Pattern 6: Global Configuration
Configure global plugins, components, and directives that all tests need. Create a custom mount function for consistency.
Test Utils Setup
// test-utils.ts
import { mount, type MountingOptions, type VueWrapper } from "@vue/test-utils";
import { createTestingPinia } from "@pinia/testing";
import type { Component } from "vue";
// Import global components your app uses
import { Button } from "@/components/ui/button.vue";
import { Input } from "@/components/ui/input.vue";
interface ExtendedMountOptions extends MountingOptions<unknown> {
initialPiniaState?: Record<string, unknown>;
}
function customMount<T extends Component>(
component: T,
options: ExtendedMountOptions = {},
): VueWrapper {
const { initialPiniaState, ...mountOptions } = options;
return mount(component, {
global: {
plugins: [
createTestingPinia({
initialState: initialPiniaState,
stubActions: false,
}),
],
components: {
Button,
Input,
},
stubs: {
// Stub router components by default
RouterLink: true,
RouterView: true,
},
...mountOptions.global,
},
...mountOptions,
});
}
export { customMount as mount };
export * from "@vue/test-utils";
Why custom mount: Avoids repeating global configuration in every test. Creates consistent test environment matching your app.
See examples/mocking.md for complete mocking examples.
</patterns>
<red_flags>
RED FLAGS
High Priority Issues:
- Not awaiting DOM-updating methods - Causes flaky tests that pass/fail randomly based on timing
- Using
shallowMountby default - Reduces test fidelity; child components don't render as expected - Using classes/IDs as selectors - Brittle tests that break when styling changes
- Forgetting
flushPromises()after API calls - Assertions run before async operations complete
Medium Priority Issues:
- Testing implementation details - Accessing
wrapper.vmdirectly instead of testing user-visible behavior - Over-mocking - Stubbing everything reduces confidence that code works in production
- Not using
createTestingPinia()- Manual store mocking is error-prone and verbose - Using
wrapper.setData()to set reactive state - Better to trigger through user interactions
Common Mistakes:
- Missing
awaitontrigger(),setValue(),setProps(),setData() - Using
find()instead ofget()for required elements (hides failures) - Not resetting mocks between tests (cross-test pollution)
- Using
wrapper.vm.someMethod()instead of triggering through UI
Gotchas and Edge Cases:
trigger('click')doesn't work on disabled elements (expected browser behavior)setValue()only works on<input>,<textarea>, and<select>elementsshallowMountstubs ALL child components including those from component librariesflushPromises()only resolves Promises - notsetTimeout(use fake timers)- Emitted events array grows across interactions - check specific index for assertions
setData()does NOT work with Composition API - it only works with Options APIdata()functionisVisible()requiresattachTo: document.bodyto work correctly
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST await all DOM-updating methods: trigger(), setValue(), setProps(), setData())
(You MUST use flushPromises() after async operations that Vue doesn't track (API calls, timers))
(You MUST use mount() by default - only use shallowMount() for performance issues or complex isolation)
(You MUST use data-test attributes for selectors, NOT classes or IDs)
(You MUST use createTestingPinia() for Pinia stores, NOT manual mocking)
Failure to follow these rules will produce flaky tests that don't reflect real component behavior.
</critical_reminders>