Vue.js 3のテスト実装入門:VitestとTest Utilsで始めるユニット・コンポーネントテスト

Vue.js 3では、Composition APIの導入や周辺ライブラリのアップデートに伴い、テストの書き方や設計方針も従来のVue 2とは異なる部分が出てきました。

本記事では、Vue.js 3でのテスト実装方法について、基本的な考え方から具体的なサンプルコードまでを詳しく解説します。Unit TestやComponent Testにおける推奨手法も含めて、備忘録的にまとめておきます。

1. Vue.js 3におけるテストの基本方針

Vue.js 3では、コンポーネントの単体テスト(Unit Test)および統合的なコンポーネントテスト(Component Test)の両方が重要視されます。特に、Composition APIの導入によって、ロジックを抽出して別モジュールに切り出すことが容易になり、テストのしやすさも向上しています。

Vue公式では、テストツールとして以下の組み合わせを推奨しています。

  • テストランナー: Vitest または Jest
  • レンダリング: @vue/test-utils
  • アサーション: expect / @testing-library/jest-dom

Vue 3特有の注意点としては、refreactiveなどのリアクティブシステムがテスト内でどのように扱われるかに注目する必要があります。また、TypeScriptを併用する場合は型チェックによる品質担保も可能です。

2. テスト環境の構築手順

まずはVue 3のテスト環境を構築します。最近ではViteVitestの組み合わせが軽量で高速なため、多くのプロジェクトで採用されています。以下はセットアップ手順です。

npm create vite@latest my-vue-app --template vue
cd my-vue-app
npm install
npm install -D vitest @vue/test-utils jsdom

vitest.config.tsに以下を追加します。

import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    globals: true,
  },
})

また、テスト用のスクリプトをpackage.jsonに追加します。

"scripts": {
  "test": "vitest"
}

この状態で、テストファイル(例: HelloWorld.spec.ts)を作成すれば準備完了です。

3. コンポーネントの単体テスト(Unit Test)

Vueの単体テストでは、特定の関数やComposition APIのロジックが正しく動作するかを検証します。たとえば、以下のようなuseCounter.tsというComposable関数をテストします。

// useCounter.ts
import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++
  return { count, increment }
}

このComposableのテストコードは以下のようになります。

// useCounter.spec.ts
import { useCounter } from './useCounter'
import { describe, it, expect } from 'vitest'

describe('useCounter', () => {
  it('初期値は0であること', () => {
    const { count } = useCounter()
    expect(count.value).toBe(0)
  })

  it('incrementで値が増加すること', () => {
    const { count, increment } = useCounter()
    increment()
    expect(count.value).toBe(1)
  })
})

このように、Composition APIを使った関数は単体テストがしやすく、状態の検証も容易です。

4. コンポーネントのレンダリングテスト

コンポーネントそのもののテストでは、Vue Test Utilsを活用してDOM操作やイベント発火を確認します。以下は簡単なコンポーネントCounter.vueの例とそのテストです。

<!-- Counter.vue -->
<template>
  <div>
    <p data-testid="count">Count: {{ count }}</p>
    <button @click="increment">+</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
</script>
// Counter.spec.ts
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'
import { describe, it, expect } from 'vitest'

describe('Counter.vue', () => {
  it('初期値が表示されていること', () => {
    const wrapper = mount(Counter)
    expect(wrapper.get('[data-testid="count"]').text()).toBe('Count: 0')
  })

  it('ボタンクリックでカウントアップすること', async () => {
    const wrapper = mount(Counter)
    await wrapper.find('button').trigger('click')
    expect(wrapper.get('[data-testid="count"]').text()).toBe('Count: 1')
  })
})

Vue Test Utilsは、マウントされたコンポーネントに対して操作を加えるだけで簡単にテストできます。また、data-testid属性を使うことで、DOMの指定もわかりやすくなります。

5. モックと依存性の注入による柔軟なテスト

Vue 3では、Composition APIやProvide/Injectを活用することで依存関係をテスト時に差し替える設計が可能です。API通信などを伴う場合はAxiosやFetchを直接呼ばず、ラッパー関数を用意してモックすることが推奨されます。

// api.ts
export async function fetchUser() {
  const res = await fetch('/api/user')
  return res.json()
}

テスト時にはこの関数をモック化できます。

// api.spec.ts
import * as api from './api'
import { describe, it, expect, vi } from 'vitest'

describe('fetchUser', () => {
  it('モックされたユーザーデータを返す', async () => {
    vi.spyOn(api, 'fetchUser').mockResolvedValue({ name: 'Taro' })
    const user = await api.fetchUser()
    expect(user.name).toBe('Taro')
  })
})

こうすることで、外部APIに依存しない安定したテストが可能になります。

6. E2Eテストの導入と考慮点

UnitやComponentレベルのテストに加えて、Vueアプリ全体の流れを確認するE2Eテストも重要です。Vue公式ではPlaywrightまたはCypressが推奨されています。

E2Eテストでは、以下の点に注意する必要があります。

  • 通信やDBアクセスをモックまたはスタブすること
  • 非同期処理の待機タイミング
  • 画面遷移やUIの整合性チェック

Cypressを例に挙げると、以下のようなテストコードでログイン画面の動作を確認できます。

// login.cy.js
describe('ログイン画面', () => {
  it('正しい資格情報でログインできる', () => {
    cy.visit('/login')
    cy.get('input[name="email"]').type('test@example.com')
    cy.get('input[name="password"]').type('password123')
    cy.get('button[type="submit"]').click()
    cy.url().should('include', '/dashboard')
  })
})

E2Eテストは実行時間が長くなるため、CI/CDパイプラインと統合し、重要なフローだけを自動化するのが現実的です。

まとめ

Vue.js 3におけるテストは、Composition APIやVite環境の導入によって、よりシンプルかつ高速に行えるようになりました。ユニットテストでロジックの正確性を担保し、Vue Test UtilsでUIコンポーネントの動作確認を行い、さらにE2Eでユーザー視点の検証を加えることで、高品質なフロントエンド開発が実現できます。

テストは開発の「最後にやるもの」ではなく、設計段階から意識することが大切です。Vue 3の力を最大限に活かすためにも、日頃からテストを書く習慣を身につけていきましょう。

参考

タイトルとURLをコピーしました