Vue — Vue Test Utils
テクノロジ系 / L07 test — テスト
Vue — Vue Test Utils
mount で render
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import Counter from "./Counter.vue";
describe("Counter", () => {
it("renders initial count", () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain("0");
});
});mount() でコンポーネントを仮想 DOM に描画。
wrapper.text() で中身の文字列。
find と click
it("increments on click", async () => {
const wrapper = mount(Counter);
const button = wrapper.find("button");
await button.trigger("click");
expect(wrapper.text()).toContain("1");
});find/findAllで要素取得trigger("click" / "input" / "keydown" 等)でイベント- 必ず await (再描画を待つため)
Props を渡す
import Greeting from "./Greeting.vue";
const wrapper = mount(Greeting, {
props: { name: "Taro", age: 20 },
});
expect(wrapper.text()).toContain("Taro");emit を検証
it("emits submit", async () => {
const wrapper = mount(LoginForm);
await wrapper.find("input").setValue("test@example.com");
await wrapper.find("form").trigger("submit");
expect(wrapper.emitted("submit")).toHaveLength(1);
expect(wrapper.emitted("submit")?.[0]).toEqual([{ email: "test@example.com" }]);
});wrapper.emitted("event") で発火履歴 (配列の配列)。
v-model のテスト
it("v-model works", async () => {
const wrapper = mount(MyInput, {
props: { modelValue: "hello" },
});
await wrapper.find("input").setValue("world");
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual(["world"]);
});update:modelValue イベントを検証。
グローバル設定 (Router / Pinia)
import { createTestingPinia } from "@pinia/testing";
mount(MyComponent, {
global: {
plugins: [createTestingPinia()],
},
});Router も同様にテスト用 Router を inject。
findByText / findByRole
@vue/test-utils 単体には無いが、@testing-library/vue を使えば RTL ライクに:
$ pnpm add -D @testing-library/vue
import { render, screen } from "@testing-library/vue";
test("greets", () => {
render(Greeting, { props: { name: "Taro" } });
expect(screen.getByText(/Taro/)).toBeInTheDocument();
});ユーザ視点のクエリで書ける。お好みで。
まとめ
mount(Component, { props, global })で描画find / trigger / setValue / emittedで操作・検証- 必ず await (再描画待ち)
- 状態管理は
createTestingPiniaで注入 - RTL 風が好きなら
@testing-library/vue
最後は Playwright (E2E)。
考えてみよう
既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。
@vue/test-utils — 演習
pnpm add -D @vue/test-utils jsdom
vitest.config.ts:
test: { environment: "jsdom" }Lv1: 基本 — mount と find
問題
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import Counter from "./Counter.vue";
describe("Counter", () => {
it("renders initial 0", () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain("0");
});
it("increments on click", async () => {
const wrapper = mount(Counter);
await wrapper.find("button").trigger("click");
expect(wrapper.text()).toContain("1");
});
});合格基準
2 test pass、必ず await を付けることを覚える。
Lv2: 応用 — props と emit 検証
問題
it("renders with props", () => {
const wrapper = mount(Greeting, {
props: { name: "Taro", age: 20 },
});
expect(wrapper.text()).toContain("Taro");
});
it("emits submit on form submit", async () => {
const wrapper = mount(LoginForm);
await wrapper.find("input[type=email]").setValue("x@y.z");
await wrapper.find("form").trigger("submit");
expect(wrapper.emitted("submit")).toBeTruthy();
expect(wrapper.emitted("submit")?.[0]).toEqual([{ email: "x@y.z" }]);
});合格基準
- props 渡しテスト
- emitted で発火履歴検証
Lv3: 発展 — Pinia + Router 込みのテスト
問題
import { createTestingPinia } from "@pinia/testing";
import { createRouter, createMemoryHistory } from "vue-router";
const router = createRouter({
history: createMemoryHistory(),
routes: [{ path: "/", component: { template: "" } }],
});
mount(MyComponent, {
global: {
plugins: [createTestingPinia(), router],
},
});ストアとルーターを inject してテスト。
合格基準
- Provider 注入できる
- ストア/ルーター依存の component がテスト可
提出
submission.md に3問の回答を。