採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

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問の回答を。