jpskill.com
🛠️ 開発・MCP コミュニティ

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本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
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
🪟 Windows (PowerShell)
$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. 1. 下の青いボタンを押して web-testing-vue-test-utils.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → web-testing-vue-test-utils フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

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/ フォルダを参照してください。
  • 意思決定フレームワークとアンチパターンについては、reference.md を参照してください。

<philosophy>

哲学

Vue Test Utils は、Vue コンポーネントを分離してマウントし、Wrapper API を通じて操作するためのユーティリティを提供します。指針となる原則は、「テストがソフトウェアの使用方法に似ているほど、より確信が得られる」です。

コア原則:

  1. 実装ではなくユーザーの行動をテストする: 内部コンポーネントの状態ではなく、ユーザーが見て操作するものをテストします。
  2. 完全なレンダリングを優先する: 現実的なテストのために、デフォルトでは mount() を使用します。shallowMount() は必要な場合にのみ使用します。
  3. アクセス可能なセレクターを使用する: 変更される可能性のあるクラスや ID ではなく、data-test 属性を優先します。
  4. 非同期処理を待機する: Vue は DOM を非同期的に更新します - 常に DOM 更新メソッドを待機します。
  5. スタブを最小限に抑える: スタブが多いほどテストの忠実度が低下します。必要なものだけをスタブします。

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 or shallowMount() for isolation. Use wrapper.find() with data-test attributes, await trigger() for events, await setValue() for inputs. Use flushPromises() for async operations. Mock Pinia stores with createTestingPinia().


<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:


<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:

  1. Test User Behavior, Not Implementation: Test what users see and interact with, not internal component state
  2. Prefer Full Rendering: Use mount() by default for realistic testing; shallowMount() only when needed
  3. Use Accessible Selectors: Prefer data-test attributes over classes or IDs that may change
  4. Await Async Operations: Vue updates the DOM asynchronously - always await DOM-updating methods
  5. 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 getComponent when the child must exist - provides clearer error messages on failure
  • Use findComponent when 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 shallowMount by 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.vm directly 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 await on trigger(), setValue(), setProps(), setData()
  • Using find() instead of get() 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> elements
  • shallowMount stubs ALL child components including those from component libraries
  • flushPromises() only resolves Promises - not setTimeout (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 API data() function
  • isVisible() requires attachTo: document.body to 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>