【Vue】Vitestの基本:Vueでテストを書く準備

JavaScript

こんにちは、かつコーチです。

前回は、VueアプリのXSS対策について解説しました。

認証・セキュリティ編はここで一区切りとなり、今回からは「テスト編」に入ります。

まずはVueプロジェクトで定番のテストランナーであるVitestの基本を押さえて、テストを書くための土台を作っていきましょう。

なぜVueアプリにテストが必要なのか

動作確認を手作業に頼るリスク

小規模なアプリのうちは、画面をポチポチ触って動作確認するだけでも何とかなります。

ただし、コンポーネントの数が増え、機能同士が絡み合ってくると話は変わってきます。

「ある機能を修正したら、別の画面が壊れていた」というのは、私自身が何度も経験してきた失敗です。

とくにcomputedやwatchで複数の状態が連動しているコンポーネントは、手作業での確認漏れが起きやすいポイントです。

テストコードを書いておけば、修正のたびに壊れていないかを機械的にチェックできます。

Vitestが選ばれる理由

VueのテストランナーにはJestを使う選択肢もありますが、近年のVueプロジェクトではVitestが主流になっています。

Vue公式のビルドツールであるViteと設定を共有できるため、追加の設定がほとんど要らずにすぐ使い始められるのが大きな理由です。

実行速度も速く、開発中に何度もテストを回すストレスが少ないのも利点です。

Vitestを導入する

インストールと設定

まずは必要なパッケージをインストールします。

npm install -D vitest @vue/test-utils jsdom

jsdomは、Node.js上でブラウザのDOM環境を再現するためのライブラリです。

Vueコンポーネントのテストでは、実際のブラウザがなくてもDOM操作を伴うテストを実行できるようにするために必要になります。

vite.config.tsに、テスト用の設定を追記します。

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

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

environment: 'jsdom'でDOM環境を指定し、globals: trueにすることで、describeitといったテスト用の関数をimportせずに使えるようになります。

package.jsonにスクリプトを追加する

テストを実行しやすいように、package.jsonにスクリプトを追加しておきます。

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}

npm run testは監視モードで起動し、ファイルを保存するたびに自動で再実行されます。

npm run test:runは1回だけ実行して終了するので、CI環境ではこちらを使うのが基本です。

最初のテストを書いてみる

シンプルな関数をテストする

いきなりコンポーネントのテストに入る前に、まずは純粋な関数のテストから慣れていきましょう。

// utils/tax.ts
export const calcTaxIncludedPrice = (price: number): number => {
  return Math.floor(price * 1.1)
}
// utils/tax.test.ts
import { describe, it, expect } from 'vitest'
import { calcTaxIncludedPrice } from './tax'

describe('calcTaxIncludedPrice', () => {
  it('税込価格を正しく計算できる', () => {
    expect(calcTaxIncludedPrice(1000)).toBe(1100)
  })

  it('端数は切り捨てられる', () => {
    expect(calcTaxIncludedPrice(999)).toBe(1098)
  })
})

describeでテストのまとまりを作り、ittestでも同じ意味)に1つずつ検証したい振る舞いを書きます。

expect(実際の値).toBe(期待する値)という形が、Vitestの基本の書き方です。

テストを実行する

npm run test:run

実行すると、成功したテストには緑のチェックマークが、失敗したテストには具体的な差分が表示されます。

「何をテストしていて、なぜ失敗したのか」が一目で分かる出力になっているのがVitestの使いやすいところです。

つまずきやすいポイント:非同期処理のテスト

私が実際にハマった「テストが終わる前に判定されてしまう」問題

APIを呼び出す処理をテストしたとき、非同期処理の待ち方を誤ってテストが正しく動かなかったことがあります。

❌ Before:awaitを忘れて非同期処理を待たずに判定してしまう

import { describe, it, expect } from 'vitest'

const fetchUserName = async (): Promise<string> => {
  return new Promise((resolve) => {
    setTimeout(() => resolve('かつコーチ'), 100)
  })
}

describe('fetchUserName', () => {
  it('ユーザー名を取得できる', () => {
    // awaitを忘れているため、Promiseそのものを比較してしまいテストが意図通り動かない
    const result = fetchUserName()
    expect(result).toBe('かつコーチ')
  })
})

このコードは、fetchUserName()の返り値がまだ解決していないPromiseオブジェクトのままなので、文字列の'かつコーチ'とは一致せず、意図した検証になっていません。

私は最初、これに気づかずエラーメッセージだけを見て「関数側が壊れている」と勘違いし、実装コードを疑って時間を使ってしまいました。

✅ After:テスト関数自体をasyncにしてawaitで結果を待つ

import { describe, it, expect } from 'vitest'

const fetchUserName = async (): Promise<string> => {
  return new Promise((resolve) => {
    setTimeout(() => resolve('かつコーチ'), 100)
  })
}

describe('fetchUserName', () => {
  it('ユーザー名を取得できる', async () => {
    const result = await fetchUserName()
    expect(result).toBe('かつコーチ')
  })
})

itのコールバック関数をasyncにし、内部でawaitすることで、非同期処理が完了してから判定が行われるようになります。

APIやタイマーが絡む処理をテストするときは、「asyncawaitをつけ忘れていないか」を必ず確認する癖をつけましょう。

応用:カバレッジを計測する

どこまでテストできているかを可視化する

Vitestには、テストがコードのどこまでをカバーしているかを計測する機能も用意されています。

npm install -D @vitest/coverage-v8
npx vitest run --coverage

実行すると、ファイルごとにテストで通過した行の割合がレポートとして表示されます。

「カバレッジ100%」を目指すこと自体が目的化しないよう注意が必要ですが、重要なロジックがテストで守られているかを確認する目安として活用できます。

次回は、@vue/test-utilsを使ってVueコンポーネントそのものをテストする方法を扱っていきます。

まとめ

この記事のポイント

  • テストコードがあれば、修正のたびに手作業で全画面を確認しなくても壊れていないかチェックできる
  • VueプロジェクトではViteと設定を共有できるVitestが定番のテストランナー
  • describeitでテストをまとめ、expect().toBe()で結果を検証する
  • 非同期処理をテストするときは、itのコールバックをasyncにしてawaitで結果を待つ必要がある
  • カバレッジ計測で、テストが実装のどこまでをカバーできているかを可視化できる

次に読むべき記事

次回は「Vue Test Utilsでコンポーネントをテストする」を解説します。

→ 次の記事:Vue Test Utilsでコンポーネントをテストする

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